1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.html;
16
17 import java.io.IOException;
18 import java.io.PrintWriter;
19 import java.io.Serializable;
20 import java.io.StringWriter;
21 import java.nio.charset.Charset;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.NoSuchElementException;
28
29 import org.htmlunit.BrowserVersionFeatures;
30 import org.htmlunit.IncorrectnessListener;
31 import org.htmlunit.Page;
32 import org.htmlunit.SgmlPage;
33 import org.htmlunit.WebAssert;
34 import org.htmlunit.WebClient;
35 import org.htmlunit.WebClient.PooledCSS3Parser;
36 import org.htmlunit.WebWindow;
37 import org.htmlunit.css.ComputedCssStyleDeclaration;
38 import org.htmlunit.css.CssStyleSheet;
39 import org.htmlunit.css.StyleAttributes;
40 import org.htmlunit.cssparser.parser.CSSErrorHandler;
41 import org.htmlunit.cssparser.parser.CSSException;
42 import org.htmlunit.cssparser.parser.CSSOMParser;
43 import org.htmlunit.cssparser.parser.CSSParseException;
44 import org.htmlunit.cssparser.parser.selector.Selector;
45 import org.htmlunit.cssparser.parser.selector.SelectorList;
46 import org.htmlunit.html.HtmlElement.DisplayStyle;
47 import org.htmlunit.html.serializer.HtmlSerializerNormalizedText;
48 import org.htmlunit.html.serializer.HtmlSerializerVisibleText;
49 import org.htmlunit.html.xpath.XPathHelper;
50 import org.htmlunit.javascript.HtmlUnitScriptable;
51 import org.htmlunit.javascript.host.event.Event;
52 import org.htmlunit.xpath.xml.utils.PrefixResolver;
53 import org.w3c.dom.DOMException;
54 import org.w3c.dom.Document;
55 import org.w3c.dom.NamedNodeMap;
56 import org.w3c.dom.Node;
57 import org.w3c.dom.UserDataHandler;
58 import org.xml.sax.SAXException;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public abstract class DomNode implements Cloneable, Serializable, Node {
83
84
85 public static final String READY_STATE_UNINITIALIZED = "uninitialized";
86
87
88 public static final String READY_STATE_LOADING = "loading";
89
90
91 public static final String READY_STATE_LOADED = "loaded";
92
93
94 public static final String READY_STATE_INTERACTIVE = "interactive";
95
96
97 public static final String READY_STATE_COMPLETE = "complete";
98
99
100 public static final String PROPERTY_ELEMENT = "element";
101
102 private static final NamedNodeMap EMPTY_NAMED_NODE_MAP = new ReadOnlyEmptyNamedNodeMapImpl();
103
104
105 private SgmlPage page_;
106
107
108 private DomNode parent_;
109
110
111
112
113
114 private DomNode previousSibling_;
115
116
117
118
119 private DomNode nextSibling_;
120
121
122 private DomNode firstChild_;
123
124
125
126
127
128 private HtmlUnitScriptable scriptObject_;
129
130
131 private String readyState_;
132
133
134
135
136 private int startLineNumber_ = -1;
137
138
139
140
141 private int startColumnNumber_ = -1;
142
143
144
145
146 private int endLineNumber_ = -1;
147
148
149
150
151 private int endColumnNumber_ = -1;
152
153 private boolean attachedToPage_;
154
155
156 private List<CharacterDataChangeListener> characterDataListeners_;
157 private List<DomChangeListener> domListeners_;
158
159 private Map<String, Object> userData_;
160
161
162
163
164
165 protected DomNode(final SgmlPage page) {
166 readyState_ = READY_STATE_LOADING;
167 page_ = page;
168 }
169
170
171
172
173
174
175
176 public void setStartLocation(final int startLineNumber, final int startColumnNumber) {
177 startLineNumber_ = startLineNumber;
178 startColumnNumber_ = startColumnNumber;
179 }
180
181
182
183
184
185
186
187 public void setEndLocation(final int endLineNumber, final int endColumnNumber) {
188 endLineNumber_ = endLineNumber;
189 endColumnNumber_ = endColumnNumber;
190 }
191
192
193
194
195
196 public int getStartLineNumber() {
197 return startLineNumber_;
198 }
199
200
201
202
203
204 public int getStartColumnNumber() {
205 return startColumnNumber_;
206 }
207
208
209
210
211
212
213 public int getEndLineNumber() {
214 return endLineNumber_;
215 }
216
217
218
219
220
221
222 public int getEndColumnNumber() {
223 return endColumnNumber_;
224 }
225
226
227
228
229
230 public SgmlPage getPage() {
231 return page_;
232 }
233
234
235
236
237
238 public HtmlPage getHtmlPageOrNull() {
239 if (page_ == null || !page_.isHtmlPage()) {
240 return null;
241 }
242 return (HtmlPage) page_;
243 }
244
245
246
247
248 @Override
249 public Document getOwnerDocument() {
250 return getPage();
251 }
252
253
254
255
256
257
258
259
260
261 public void setScriptableObject(final HtmlUnitScriptable scriptObject) {
262 scriptObject_ = scriptObject;
263 }
264
265
266
267
268 @Override
269 public DomNode getLastChild() {
270 if (firstChild_ != null) {
271
272 return firstChild_.previousSibling_;
273 }
274 return null;
275 }
276
277
278
279
280 @Override
281 public DomNode getParentNode() {
282 return parent_;
283 }
284
285
286
287
288
289 protected void setParentNode(final DomNode parent) {
290 parent_ = parent;
291 }
292
293
294
295
296
297 public int getIndex() {
298 int index = 0;
299 for (DomNode n = previousSibling_; n != null && n.nextSibling_ != null; n = n.previousSibling_) {
300 index++;
301 }
302 return index;
303 }
304
305
306
307
308 @Override
309 public DomNode getPreviousSibling() {
310 if (parent_ == null || this == parent_.firstChild_) {
311
312 return null;
313 }
314 return previousSibling_;
315 }
316
317
318
319
320 @Override
321 public DomNode getNextSibling() {
322 return nextSibling_;
323 }
324
325
326
327
328 @Override
329 public DomNode getFirstChild() {
330 return firstChild_;
331 }
332
333
334
335
336
337
338
339 public boolean isAncestorOf(final DomNode node) {
340 DomNode parent = node;
341 while (parent != null) {
342 if (parent == this) {
343 return true;
344 }
345 parent = parent.getParentNode();
346 }
347 return false;
348 }
349
350
351
352
353
354
355
356 public boolean isAncestorOfAny(final DomNode... nodes) {
357 for (final DomNode node : nodes) {
358 if (isAncestorOf(node)) {
359 return true;
360 }
361 }
362 return false;
363 }
364
365
366
367
368 @Override
369 public String getNamespaceURI() {
370 return null;
371 }
372
373
374
375
376 @Override
377 public String getLocalName() {
378 return null;
379 }
380
381
382
383
384 @Override
385 public String getPrefix() {
386 return null;
387 }
388
389
390
391
392 @Override
393 public boolean hasChildNodes() {
394 return firstChild_ != null;
395 }
396
397
398
399
400 @Override
401 public DomNodeList<DomNode> getChildNodes() {
402 return new SiblingDomNodeList(this);
403 }
404
405
406
407
408
409 @Override
410 public boolean isSupported(final String namespace, final String featureName) {
411 throw new UnsupportedOperationException("DomNode.isSupported is not yet implemented.");
412 }
413
414
415
416
417 @Override
418 public void normalize() {
419 for (DomNode child = getFirstChild(); child != null; child = child.getNextSibling()) {
420 if (child instanceof DomText) {
421 final StringBuilder dataBuilder = new StringBuilder();
422 DomNode toRemove = child;
423 DomText firstText = null;
424
425 while (toRemove instanceof DomText && !(toRemove instanceof DomCDataSection)) {
426 final DomNode nextChild = toRemove.getNextSibling();
427 dataBuilder.append(toRemove.getTextContent());
428 if (firstText != null) {
429 toRemove.remove();
430 }
431 if (firstText == null) {
432 firstText = (DomText) toRemove;
433 }
434 toRemove = nextChild;
435 }
436 if (firstText != null) {
437 firstText.setData(dataBuilder.toString());
438 }
439 }
440 }
441 }
442
443
444
445
446 @Override
447 public String getBaseURI() {
448 return getPage().getUrl().toExternalForm();
449 }
450
451
452
453
454 @Override
455 public short compareDocumentPosition(final Node other) {
456 if (other == this) {
457 return 0;
458 }
459
460
461 final List<Node> myAncestors = getAncestors();
462 final List<Node> otherAncestors = ((DomNode) other).getAncestors();
463
464 if (!myAncestors.get(0).equals(otherAncestors.get(0))) {
465
466
467
468
469
470
471 if (this.hashCode() < other.hashCode()) {
472 return DOCUMENT_POSITION_DISCONNECTED
473 | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
474 | DOCUMENT_POSITION_PRECEDING;
475 }
476
477 return DOCUMENT_POSITION_DISCONNECTED
478 | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
479 | DOCUMENT_POSITION_FOLLOWING;
480 }
481
482 final int max = Math.min(myAncestors.size(), otherAncestors.size());
483
484 int i = 1;
485 while (i < max && myAncestors.get(i) == otherAncestors.get(i)) {
486 i++;
487 }
488
489 if (i != 1 && i == max) {
490 if (myAncestors.size() == max) {
491 return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING;
492 }
493 return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING;
494 }
495
496 if (max == 1) {
497 if (myAncestors.contains(other)) {
498 return DOCUMENT_POSITION_CONTAINS;
499 }
500 if (otherAncestors.contains(this)) {
501 return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING;
502 }
503 return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
504 }
505
506
507 final Node myAncestor = myAncestors.get(i);
508 final Node otherAncestor = otherAncestors.get(i);
509 Node node = myAncestor;
510 while (node != otherAncestor && node != null) {
511 node = node.getPreviousSibling();
512 }
513 if (node == null) {
514 return DOCUMENT_POSITION_FOLLOWING;
515 }
516 return DOCUMENT_POSITION_PRECEDING;
517 }
518
519
520
521
522
523
524
525 public List<Node> getAncestors() {
526 final List<Node> list = new ArrayList<>();
527 list.add(this);
528
529 Node node = getParentNode();
530 while (node != null) {
531 list.add(0, node);
532 node = node.getParentNode();
533 }
534 return list;
535 }
536
537
538
539
540 @Override
541 public String getTextContent() {
542 switch (getNodeType()) {
543 case ELEMENT_NODE:
544 case ATTRIBUTE_NODE:
545 case ENTITY_NODE:
546 case ENTITY_REFERENCE_NODE:
547 case DOCUMENT_FRAGMENT_NODE:
548 final StringBuilder builder = new StringBuilder();
549 for (final DomNode child : getChildren()) {
550 final short childType = child.getNodeType();
551 if (childType != COMMENT_NODE && childType != PROCESSING_INSTRUCTION_NODE) {
552 builder.append(child.getTextContent());
553 }
554 }
555 return builder.toString();
556
557 case TEXT_NODE:
558 case CDATA_SECTION_NODE:
559 case COMMENT_NODE:
560 case PROCESSING_INSTRUCTION_NODE:
561 return getNodeValue();
562
563 default:
564 return null;
565 }
566 }
567
568
569
570
571 @Override
572 public void setTextContent(final String textContent) {
573 removeAllChildren();
574 if (textContent != null && !textContent.isEmpty()) {
575 appendChild(new DomText(getPage(), textContent));
576 }
577 }
578
579
580
581
582 @Override
583 public boolean isSameNode(final Node other) {
584 return other == this;
585 }
586
587
588
589
590
591 @Override
592 public String lookupPrefix(final String namespaceURI) {
593 throw new UnsupportedOperationException("DomNode.lookupPrefix is not yet implemented.");
594 }
595
596
597
598
599
600 @Override
601 public boolean isDefaultNamespace(final String namespaceURI) {
602 throw new UnsupportedOperationException("DomNode.isDefaultNamespace is not yet implemented.");
603 }
604
605
606
607
608
609 @Override
610 public String lookupNamespaceURI(final String prefix) {
611 throw new UnsupportedOperationException("DomNode.lookupNamespaceURI is not yet implemented.");
612 }
613
614
615
616
617
618 @Override
619 public boolean isEqualNode(final Node arg) {
620 throw new UnsupportedOperationException("DomNode.isEqualNode is not yet implemented.");
621 }
622
623
624
625
626
627 @Override
628 public Object getFeature(final String feature, final String version) {
629 throw new UnsupportedOperationException("DomNode.getFeature is not yet implemented.");
630 }
631
632
633
634
635 @Override
636 public Object getUserData(final String key) {
637 Object value = null;
638 if (userData_ != null) {
639 value = userData_.get(key);
640 }
641 return value;
642 }
643
644
645
646
647 @Override
648 public Object setUserData(final String key, final Object data, final UserDataHandler handler) {
649 if (userData_ == null) {
650 userData_ = new HashMap<>();
651 }
652 return userData_.put(key, data);
653 }
654
655
656
657
658 @Override
659 public boolean hasAttributes() {
660 return false;
661 }
662
663
664
665
666 @Override
667 public NamedNodeMap getAttributes() {
668 return EMPTY_NAMED_NODE_MAP;
669 }
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685 public boolean isDisplayed() {
686 if (!mayBeDisplayed()) {
687 return false;
688 }
689
690 final Page page = getPage();
691 final WebWindow window = page.getEnclosingWindow();
692 final WebClient webClient = window.getWebClient();
693 if (webClient.getOptions().isCssEnabled()) {
694
695
696 final List<Node> ancestors = getAncestors();
697 final ArrayList<ComputedCssStyleDeclaration> styles = new ArrayList<>(ancestors.size());
698
699 for (final Node node : ancestors) {
700 if (node instanceof HtmlElement elem) {
701 if (elem.isHidden()) {
702 return false;
703 }
704
705 if (elem instanceof HtmlDialog dialog) {
706 if (!dialog.isOpen()) {
707 return false;
708 }
709 }
710 else {
711 final ComputedCssStyleDeclaration style = window.getComputedStyle(elem, null);
712 if (DisplayStyle.NONE.value().equals(style.getDisplay())) {
713 return false;
714 }
715 styles.add(style);
716 }
717 }
718 }
719
720
721
722 for (int i = styles.size() - 1; i >= 0; i--) {
723 final ComputedCssStyleDeclaration style = styles.get(i);
724 final String visibility = style.getStyleAttribute(StyleAttributes.Definition.VISIBILITY, true);
725 if (visibility.length() > 5) {
726 if ("visible".equals(visibility)) {
727 return true;
728 }
729 if ("hidden".equals(visibility) || "collapse".equals(visibility)) {
730 return false;
731 }
732 }
733 }
734 }
735 return true;
736 }
737
738
739
740
741
742
743
744
745 public boolean mayBeDisplayed() {
746 return true;
747 }
748
749
750
751
752
753
754
755
756 public String asNormalizedText() {
757 final HtmlSerializerNormalizedText ser = new HtmlSerializerNormalizedText();
758 return ser.asText(this);
759 }
760
761
762
763
764
765
766
767
768
769
770
771 public String getVisibleText() {
772 final HtmlSerializerVisibleText ser = new HtmlSerializerVisibleText();
773 return ser.asText(this);
774 }
775
776
777
778
779
780
781
782
783
784 public String asXml() {
785 Charset charsetName = null;
786 final HtmlPage htmlPage = getHtmlPageOrNull();
787 if (htmlPage != null) {
788 charsetName = htmlPage.getCharset();
789 }
790
791 final StringWriter stringWriter = new StringWriter();
792 try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
793 boolean tag = false;
794 if (charsetName != null && this instanceof HtmlHtml) {
795 printWriter.print("<?xml version=\"1.0\" encoding=\"");
796 printWriter.print(charsetName);
797 printWriter.print("\"?>");
798 tag = true;
799 }
800 printXml("", tag, printWriter);
801 return stringWriter.toString();
802 }
803 }
804
805
806
807
808
809
810
811
812
813 protected boolean printXml(final String indent, final boolean tagBefore, final PrintWriter printWriter) {
814 if (tagBefore) {
815 printWriter.print("\r\n");
816 printWriter.print(indent);
817 }
818 printWriter.print(this);
819 return printChildrenAsXml(indent, false, printWriter);
820 }
821
822
823
824
825
826
827
828
829
830 protected boolean printChildrenAsXml(final String indent, final boolean tagBefore, final PrintWriter printWriter) {
831 DomNode child = getFirstChild();
832 boolean tag = tagBefore;
833 while (child != null) {
834 tag = child.printXml(indent + " ", tag, printWriter);
835 child = child.getNextSibling();
836 }
837 return tag;
838 }
839
840
841
842
843 @Override
844 public String getNodeValue() {
845 return null;
846 }
847
848
849
850
851 @Override
852 public DomNode cloneNode(final boolean deep) {
853 final DomNode newnode;
854 try {
855 newnode = (DomNode) clone();
856 }
857 catch (final CloneNotSupportedException e) {
858 throw new IllegalStateException("Clone not supported for node [" + this + "]", e);
859 }
860
861 newnode.parent_ = null;
862 newnode.nextSibling_ = null;
863 newnode.previousSibling_ = null;
864 newnode.scriptObject_ = null;
865 newnode.firstChild_ = null;
866 newnode.attachedToPage_ = false;
867
868
869 if (deep) {
870 for (DomNode child = firstChild_; child != null; child = child.nextSibling_) {
871 newnode.appendChild(child.cloneNode(true));
872 }
873 }
874
875 return newnode;
876 }
877
878
879
880
881
882
883
884
885
886
887
888
889 @SuppressWarnings("unchecked")
890 public <T extends HtmlUnitScriptable> T getScriptableObject() {
891 if (scriptObject_ == null) {
892 final SgmlPage page = getPage();
893 if (this == page) {
894 final StringBuilder msg = new StringBuilder("No script object associated with the Page.");
895
896 msg.append(" class: '")
897 .append(page.getClass().getName())
898 .append('\'');
899 try {
900 msg.append(" url: '")
901 .append(page.getUrl()).append("' content: ")
902 .append(page.getWebResponse().getContentAsString());
903 }
904 catch (final Exception e) {
905
906 msg.append(" no details: '").append(e).append('\'');
907 }
908 throw new IllegalStateException(msg.toString());
909 }
910 scriptObject_ = page.getScriptableObject().makeScriptableFor(this);
911 }
912 return (T) scriptObject_;
913 }
914
915
916
917
918 @Override
919 public DomNode appendChild(final Node node) {
920 if (node == this) {
921 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Can not add not to itself " + this);
922 }
923 final DomNode domNode = (DomNode) node;
924 if (domNode.isAncestorOf(this)) {
925 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Can not add (grand)parent to itself " + this);
926 }
927
928 if (domNode instanceof DomDocumentFragment fragment) {
929 for (final DomNode child : fragment.getChildren()) {
930 appendChild(child);
931 }
932 }
933 else {
934
935 if (domNode.getParentNode() != null) {
936 domNode.detach();
937 }
938
939 basicAppend(domNode);
940
941 fireAddition(domNode);
942 }
943
944 return domNode;
945 }
946
947
948
949
950
951
952
953 private void basicAppend(final DomNode node) {
954
955
956 node.setPage(getPage());
957 node.parent_ = this;
958
959 if (firstChild_ == null) {
960 firstChild_ = node;
961 }
962 else {
963 final DomNode last = getLastChild();
964 node.previousSibling_ = last;
965 node.nextSibling_ = null;
966
967 last.nextSibling_ = node;
968 }
969 firstChild_.previousSibling_ = node;
970 }
971
972
973
974
975 @Override
976 public Node insertBefore(final Node newChild, final Node refChild) {
977 if (newChild instanceof DomDocumentFragment fragment) {
978 for (final DomNode child : fragment.getChildren()) {
979 insertBefore(child, refChild);
980 }
981 return newChild;
982 }
983
984 if (refChild == null) {
985 appendChild(newChild);
986 return newChild;
987 }
988
989 if (refChild.getParentNode() != this) {
990 throw new DOMException(DOMException.NOT_FOUND_ERR, "Reference node is not a child of this node.");
991 }
992
993 ((DomNode) refChild).insertBefore((DomNode) newChild);
994 return newChild;
995 }
996
997
998
999
1000
1001
1002
1003 public void insertBefore(final DomNode newNode) {
1004 if (previousSibling_ == null) {
1005 throw new IllegalStateException("Previous sibling for " + this + " is null.");
1006 }
1007
1008 if (newNode == this) {
1009 return;
1010 }
1011
1012 if (newNode instanceof DomDocumentFragment) {
1013 for (final DomNode child : newNode.getChildren()) {
1014 insertBefore(child);
1015 }
1016 return;
1017 }
1018
1019
1020 if (newNode.getParentNode() != null) {
1021 newNode.detach();
1022 }
1023
1024 basicInsertBefore(newNode);
1025
1026 fireAddition(newNode);
1027 }
1028
1029
1030
1031
1032
1033
1034
1035 private void basicInsertBefore(final DomNode node) {
1036
1037
1038 node.setPage(page_);
1039 node.parent_ = parent_;
1040 node.previousSibling_ = previousSibling_;
1041 node.nextSibling_ = this;
1042
1043 if (parent_.firstChild_ == this) {
1044 parent_.firstChild_ = node;
1045 }
1046 else {
1047 previousSibling_.nextSibling_ = node;
1048 }
1049 previousSibling_ = node;
1050 }
1051
1052 private void fireAddition(final DomNode domNode) {
1053 final boolean wasAlreadyAttached = domNode.isAttachedToPage();
1054 domNode.attachedToPage_ = isAttachedToPage();
1055
1056 final SgmlPage page = getPage();
1057 if (domNode.attachedToPage_) {
1058
1059 if (null != page && page.isHtmlPage()) {
1060 ((HtmlPage) page).notifyNodeAdded(domNode);
1061 }
1062
1063
1064 if (!domNode.isBodyParsed() && !wasAlreadyAttached) {
1065 if (domNode.getFirstChild() != null) {
1066 for (final Iterator<DomNode> iterator =
1067 domNode.new DescendantDomNodesIterator(); iterator.hasNext();) {
1068 final DomNode child = iterator.next();
1069 child.attachedToPage_ = true;
1070 child.onAllChildrenAddedToPage(true);
1071 }
1072 }
1073 domNode.onAllChildrenAddedToPage(true);
1074 }
1075 }
1076
1077 if (this instanceof DomDocumentFragment) {
1078 onAddedToDocumentFragment();
1079 }
1080
1081 if (page == null || page.isDomChangeListenerInUse()) {
1082 fireNodeAdded(this, domNode);
1083 }
1084 }
1085
1086
1087
1088
1089
1090 private boolean isBodyParsed() {
1091 return getStartLineNumber() != -1 && getEndLineNumber() == -1;
1092 }
1093
1094
1095
1096
1097
1098 private void setPage(final SgmlPage newPage) {
1099 if (page_ == newPage) {
1100 return;
1101 }
1102
1103 page_ = newPage;
1104 for (final DomNode node : getChildren()) {
1105 node.setPage(newPage);
1106 }
1107 }
1108
1109
1110
1111
1112 @Override
1113 public Node removeChild(final Node child) {
1114 if (child.getParentNode() != this) {
1115 throw new DOMException(DOMException.NOT_FOUND_ERR, "Node is not a child of this node.");
1116 }
1117 ((DomNode) child).remove();
1118 return child;
1119 }
1120
1121
1122
1123
1124 public void removeAllChildren() {
1125 while (getFirstChild() != null) {
1126 getFirstChild().remove();
1127 }
1128 }
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138 public void parseHtmlSnippet(final String source) throws SAXException, IOException {
1139 final WebClient webClient = getPage().getWebClient();
1140 webClient.getPageCreator().getHtmlParser().parseFragment(webClient, this, this, source, false);
1141 }
1142
1143
1144
1145
1146 public void remove() {
1147
1148 detach();
1149 }
1150
1151
1152
1153
1154
1155
1156
1157 protected void detach() {
1158 final DomNode exParent = parent_;
1159
1160 basicRemove();
1161
1162 fireRemoval(exParent);
1163 }
1164
1165
1166
1167
1168 protected void basicRemove() {
1169 basicDetach();
1170
1171 nextSibling_ = null;
1172 previousSibling_ = null;
1173 parent_ = null;
1174 attachedToPage_ = false;
1175 for (final DomNode descendant : getDescendants()) {
1176 descendant.attachedToPage_ = false;
1177 }
1178 }
1179
1180
1181
1182
1183 private void basicDetach() {
1184 if (parent_ != null && parent_.firstChild_ == this) {
1185 parent_.firstChild_ = nextSibling_;
1186 }
1187 else if (previousSibling_ != null && previousSibling_.nextSibling_ == this) {
1188 previousSibling_.nextSibling_ = nextSibling_;
1189 }
1190 if (nextSibling_ != null && nextSibling_.previousSibling_ == this) {
1191 nextSibling_.previousSibling_ = previousSibling_;
1192 }
1193 if (parent_ != null && parent_.getLastChild() == this) {
1194 parent_.firstChild_.previousSibling_ = previousSibling_;
1195 }
1196 }
1197
1198 private void fireRemoval(final DomNode exParent) {
1199 final SgmlPage page = getPage();
1200 if (page instanceof HtmlPage htmlPage) {
1201
1202
1203 parent_ = exParent;
1204 htmlPage.notifyNodeRemoved(this);
1205 parent_ = null;
1206 }
1207
1208 if (exParent != null && (page == null || page.isDomChangeListenerInUse())) {
1209 fireNodeDeleted(exParent, this);
1210
1211 exParent.fireNodeDeleted(exParent, this);
1212 }
1213 }
1214
1215
1216
1217
1218 @Override
1219 public Node replaceChild(final Node newChild, final Node oldChild) {
1220 if (oldChild.getParentNode() != this) {
1221 throw new DOMException(DOMException.NOT_FOUND_ERR, "Node is not a child of this node.");
1222 }
1223 ((DomNode) oldChild).replace((DomNode) newChild);
1224 return oldChild;
1225 }
1226
1227
1228
1229
1230
1231
1232 public void replace(final DomNode newNode) {
1233 if (newNode != this) {
1234 final DomNode exParent = parent_;
1235 final DomNode exNextSibling = nextSibling_;
1236
1237 remove();
1238
1239 exParent.insertBefore(newNode, exNextSibling);
1240 }
1241 }
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252 public void quietlyRemoveAndMoveChildrenTo(final DomNode destination) {
1253 if (destination.getPage() != getPage()) {
1254 throw new RuntimeException("Cannot perform quiet move on nodes from different pages.");
1255 }
1256 for (final DomNode child : getChildren()) {
1257 if (child != destination) {
1258 child.basicRemove();
1259 destination.basicAppend(child);
1260 }
1261 }
1262 basicRemove();
1263 }
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277 protected void checkChildHierarchy(final Node newChild) throws DOMException {
1278 Node parentNode = this;
1279 while (parentNode != null) {
1280 if (parentNode == newChild) {
1281 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "Child node is already a parent.");
1282 }
1283 parentNode = parentNode.getParentNode();
1284 }
1285 final Document thisDocument = getOwnerDocument();
1286 final Document childDocument = newChild.getOwnerDocument();
1287 if (childDocument != thisDocument && childDocument != null) {
1288 throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Child node " + newChild.getNodeName()
1289 + " is not in the same Document as this " + getNodeName() + ".");
1290 }
1291 }
1292
1293
1294
1295
1296
1297
1298
1299 protected void onAddedToPage() {
1300 if (firstChild_ != null) {
1301 for (final DomNode child : getChildren()) {
1302 child.onAddedToPage();
1303 }
1304 }
1305 }
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315 public void onAllChildrenAddedToPage(final boolean postponed) {
1316
1317 }
1318
1319
1320
1321
1322
1323
1324
1325 protected void onAddedToDocumentFragment() {
1326 if (firstChild_ != null) {
1327 for (final DomNode child : getChildren()) {
1328 child.onAddedToDocumentFragment();
1329 }
1330 }
1331 }
1332
1333
1334
1335
1336
1337
1338
1339
1340 public void moveBefore(final DomNode movedDomNode, final DomNode referenceDomNode) {
1341 if (movedDomNode == referenceDomNode) {
1342 return;
1343 }
1344
1345 if (movedDomNode instanceof DomDocumentFragment fragment) {
1346 for (final DomNode child : fragment.getChildren()) {
1347 moveBefore(child, referenceDomNode);
1348 }
1349 return;
1350 }
1351
1352
1353 if (referenceDomNode != null && movedDomNode.getNextSibling() == referenceDomNode) {
1354 return;
1355 }
1356
1357 if (movedDomNode.isAncestorOf(this)) {
1358 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
1359 "The new child element contains the parent.");
1360 }
1361
1362 if (referenceDomNode != null && !this.isAncestorOf(referenceDomNode)) {
1363 throw new DOMException(DOMException.NOT_FOUND_ERR,
1364 "The node before which the new node is to be inserted is not a child of this node.");
1365 }
1366
1367 if (referenceDomNode != null && referenceDomNode.isAttachedToPage() && !movedDomNode.isAttachedToPage()) {
1368 throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
1369 "State-preserving atomic move cannot be performed on nodes participating in an invalid hierarchy.");
1370 }
1371
1372 if (referenceDomNode == null) {
1373 appendChild(movedDomNode);
1374 return;
1375 }
1376
1377 referenceDomNode.moveBefore(movedDomNode);
1378 }
1379
1380
1381
1382
1383
1384
1385
1386 public void moveBefore(final DomNode movedDomNode) {
1387 if (previousSibling_ == null) {
1388 throw new IllegalStateException("Previous sibling for " + this + " is null.");
1389 }
1390
1391 if (movedDomNode == this) {
1392 return;
1393 }
1394
1395 movedDomNode.detach();
1396 basicInsertBefore(movedDomNode);
1397
1398 fireAddition(movedDomNode);
1399 }
1400
1401
1402
1403
1404 public final Iterable<DomNode> getChildren() {
1405 return () -> new ChildIterator(firstChild_);
1406 }
1407
1408
1409
1410
1411 protected static class ChildIterator implements Iterator<DomNode> {
1412
1413 private DomNode nextNode_;
1414 private DomNode currentNode_;
1415
1416 public ChildIterator(final DomNode nextNode) {
1417 nextNode_ = nextNode;
1418 }
1419
1420
1421 @Override
1422 public boolean hasNext() {
1423 return nextNode_ != null;
1424 }
1425
1426
1427 @Override
1428 public DomNode next() {
1429 if (nextNode_ != null) {
1430 currentNode_ = nextNode_;
1431 nextNode_ = nextNode_.nextSibling_;
1432 return currentNode_;
1433 }
1434 throw new NoSuchElementException();
1435 }
1436
1437
1438 @Override
1439 public void remove() {
1440 if (currentNode_ == null) {
1441 throw new IllegalStateException();
1442 }
1443 currentNode_.remove();
1444 }
1445 }
1446
1447
1448
1449
1450
1451
1452
1453 public final Iterable<DomNode> getDescendants() {
1454 return () -> new DescendantDomNodesIterator();
1455 }
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465 public final Iterable<HtmlElement> getHtmlElementDescendants() {
1466 return () -> new DescendantHtmlElementsIterator();
1467 }
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477 public final Iterable<DomElement> getDomElementDescendants() {
1478 return () -> new DescendantDomElementsIterator();
1479 }
1480
1481
1482
1483
1484 protected final class DescendantDomNodesIterator implements Iterator<DomNode> {
1485 private DomNode currentNode_;
1486 private DomNode nextNode_;
1487
1488
1489
1490
1491 public DescendantDomNodesIterator() {
1492 nextNode_ = DomNode.this.getFirstChild();
1493 }
1494
1495
1496 @Override
1497 public boolean hasNext() {
1498 return nextNode_ != null;
1499 }
1500
1501
1502 @Override
1503 public DomNode next() {
1504 return nextNode();
1505 }
1506
1507
1508 @Override
1509 public void remove() {
1510 if (currentNode_ == null) {
1511 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1512 }
1513 final DomNode current = currentNode_;
1514 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1515 next();
1516 }
1517 current.remove();
1518 }
1519
1520
1521
1522
1523 public DomNode nextNode() {
1524 currentNode_ = nextNode_;
1525
1526 DomNode next = nextNode_.getFirstChild();
1527 if (next == null) {
1528 next = nextNode_.getNextSibling();
1529 }
1530 if (next == null) {
1531 next = getNextElementUpwards(nextNode_);
1532 }
1533 nextNode_ = next;
1534
1535 return currentNode_;
1536 }
1537
1538 private DomNode getNextElementUpwards(final DomNode startingNode) {
1539 if (startingNode == DomNode.this) {
1540 return null;
1541 }
1542
1543 DomNode parent = startingNode.getParentNode();
1544 while (parent != null && parent != DomNode.this) {
1545 final DomNode next = parent.getNextSibling();
1546 if (next != null) {
1547 return next;
1548 }
1549 parent = parent.getParentNode();
1550 }
1551 return null;
1552 }
1553 }
1554
1555
1556
1557
1558 protected final class DescendantDomElementsIterator implements Iterator<DomElement> {
1559 private DomNode currentNode_;
1560 private DomNode nextNode_;
1561
1562
1563
1564
1565 public DescendantDomElementsIterator() {
1566 nextNode_ = getFirstChildElement(DomNode.this);
1567 }
1568
1569
1570 @Override
1571 public boolean hasNext() {
1572 return nextNode_ != null;
1573 }
1574
1575
1576 @Override
1577 public DomElement next() {
1578 return nextNode();
1579 }
1580
1581
1582 @Override
1583 public void remove() {
1584 if (currentNode_ == null) {
1585 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1586 }
1587 final DomNode current = currentNode_;
1588 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1589 next();
1590 }
1591 current.remove();
1592 }
1593
1594
1595
1596
1597 public DomElement nextNode() {
1598 currentNode_ = nextNode_;
1599
1600 DomNode next = getFirstChildElement(nextNode_);
1601 if (next == null) {
1602 next = getNextDomSibling(nextNode_);
1603 }
1604 if (next == null) {
1605 next = getNextElementUpwards(nextNode_);
1606 }
1607 nextNode_ = next;
1608
1609 return (DomElement) currentNode_;
1610 }
1611
1612 private DomNode getNextElementUpwards(final DomNode startingNode) {
1613 if (startingNode == DomNode.this) {
1614 return null;
1615 }
1616
1617 DomNode parent = startingNode.getParentNode();
1618 while (parent != null && parent != DomNode.this) {
1619 DomNode next = parent.getNextSibling();
1620 while (next != null && !isAccepted(next)) {
1621 next = next.getNextSibling();
1622 }
1623 if (next != null) {
1624 return next;
1625 }
1626 parent = parent.getParentNode();
1627 }
1628 return null;
1629 }
1630
1631 private DomNode getFirstChildElement(final DomNode parent) {
1632 DomNode node = parent.getFirstChild();
1633 while (node != null && !isAccepted(node)) {
1634 node = node.getNextSibling();
1635 }
1636 return node;
1637 }
1638
1639
1640
1641
1642
1643
1644 private boolean isAccepted(final DomNode node) {
1645 return DomElement.class.isAssignableFrom(node.getClass());
1646 }
1647
1648 private DomNode getNextDomSibling(final DomNode element) {
1649 DomNode node = element.getNextSibling();
1650 while (node != null && !isAccepted(node)) {
1651 node = node.getNextSibling();
1652 }
1653 return node;
1654 }
1655 }
1656
1657
1658
1659
1660 protected final class DescendantHtmlElementsIterator implements Iterator<HtmlElement> {
1661 private DomNode currentNode_;
1662 private DomNode nextNode_;
1663
1664
1665
1666
1667 public DescendantHtmlElementsIterator() {
1668 nextNode_ = getFirstChildElement(DomNode.this);
1669 }
1670
1671
1672 @Override
1673 public boolean hasNext() {
1674 return nextNode_ != null;
1675 }
1676
1677
1678 @Override
1679 public HtmlElement next() {
1680 return nextNode();
1681 }
1682
1683
1684 @Override
1685 public void remove() {
1686 if (currentNode_ == null) {
1687 throw new IllegalStateException("Unable to remove current node, because there is no current node.");
1688 }
1689 final DomNode current = currentNode_;
1690 while (nextNode_ != null && current.isAncestorOf(nextNode_)) {
1691 next();
1692 }
1693 current.remove();
1694 }
1695
1696
1697
1698
1699 public HtmlElement nextNode() {
1700 currentNode_ = nextNode_;
1701
1702 DomNode next = getFirstChildElement(nextNode_);
1703 if (next == null) {
1704 next = getNextDomSibling(nextNode_);
1705 }
1706 if (next == null) {
1707 next = getNextElementUpwards(nextNode_);
1708 }
1709 nextNode_ = next;
1710
1711 return (HtmlElement) currentNode_;
1712 }
1713
1714 private DomNode getNextElementUpwards(final DomNode startingNode) {
1715 if (startingNode == DomNode.this) {
1716 return null;
1717 }
1718
1719 DomNode parent = startingNode.getParentNode();
1720 while (parent != null && parent != DomNode.this) {
1721 DomNode next = parent.getNextSibling();
1722 while (next != null && !isAccepted(next)) {
1723 next = next.getNextSibling();
1724 }
1725 if (next != null) {
1726 return next;
1727 }
1728 parent = parent.getParentNode();
1729 }
1730 return null;
1731 }
1732
1733 private DomNode getFirstChildElement(final DomNode parent) {
1734 DomNode node = parent.getFirstChild();
1735 while (node != null && !isAccepted(node)) {
1736 node = node.getNextSibling();
1737 }
1738 return node;
1739 }
1740
1741
1742
1743
1744
1745
1746 private boolean isAccepted(final DomNode node) {
1747 return HtmlElement.class.isAssignableFrom(node.getClass());
1748 }
1749
1750 private DomNode getNextDomSibling(final DomNode element) {
1751 DomNode node = element.getNextSibling();
1752 while (node != null && !isAccepted(node)) {
1753 node = node.getNextSibling();
1754 }
1755 return node;
1756 }
1757 }
1758
1759
1760
1761
1762
1763 public String getReadyState() {
1764 return readyState_;
1765 }
1766
1767
1768
1769
1770
1771 public void setReadyState(final String state) {
1772 readyState_ = state;
1773 }
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789 public <T> List<T> getByXPath(final String xpathExpr) {
1790 return XPathHelper.getByXPath(this, xpathExpr, null);
1791 }
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802 public List<?> getByXPath(final String xpathExpr, final PrefixResolver resolver) {
1803 return XPathHelper.getByXPath(this, xpathExpr, resolver);
1804 }
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816 public <X> X getFirstByXPath(final String xpathExpr) {
1817 return getFirstByXPath(xpathExpr, null);
1818 }
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831 @SuppressWarnings("unchecked")
1832 public <X> X getFirstByXPath(final String xpathExpr, final PrefixResolver resolver) {
1833 final List<?> results = getByXPath(xpathExpr, resolver);
1834 if (results.isEmpty()) {
1835 return null;
1836 }
1837 return (X) results.get(0);
1838 }
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851 public String getCanonicalXPath() {
1852 throw new RuntimeException("Method getCanonicalXPath() not implemented for nodes of type " + getNodeType());
1853 }
1854
1855
1856
1857
1858
1859 protected void notifyIncorrectness(final String message) {
1860 final WebClient client = getPage().getEnclosingWindow().getWebClient();
1861 final IncorrectnessListener incorrectnessListener = client.getIncorrectnessListener();
1862 incorrectnessListener.notify(message, this);
1863 }
1864
1865
1866
1867
1868
1869
1870
1871
1872 public void addDomChangeListener(final DomChangeListener listener) {
1873 WebAssert.notNull("listener", listener);
1874
1875 synchronized (this) {
1876 if (domListeners_ == null) {
1877 domListeners_ = new ArrayList<>();
1878 }
1879 domListeners_.add(listener);
1880
1881 final SgmlPage page = getPage();
1882 if (page != null) {
1883 page.domChangeListenerAdded();
1884 }
1885 }
1886 }
1887
1888
1889
1890
1891
1892
1893
1894
1895 public void removeDomChangeListener(final DomChangeListener listener) {
1896 WebAssert.notNull("listener", listener);
1897
1898 synchronized (this) {
1899 if (domListeners_ != null) {
1900 domListeners_.remove(listener);
1901 }
1902 }
1903 }
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914 protected void fireNodeAdded(final DomNode parentNode, final DomNode addedNode) {
1915 DomChangeEvent event = null;
1916
1917 DomNode toInform = this;
1918 while (toInform != null) {
1919 if (toInform.domListeners_ != null) {
1920 final List<DomChangeListener> listeners;
1921 synchronized (toInform) {
1922 listeners = new ArrayList<>(toInform.domListeners_);
1923 }
1924
1925 if (event == null) {
1926 event = new DomChangeEvent(parentNode, addedNode);
1927 }
1928 for (final DomChangeListener domChangeListener : listeners) {
1929 domChangeListener.nodeAdded(event);
1930 }
1931 }
1932
1933 toInform = toInform.getParentNode();
1934 }
1935 }
1936
1937
1938
1939
1940
1941
1942
1943
1944 public void addCharacterDataChangeListener(final CharacterDataChangeListener listener) {
1945 WebAssert.notNull("listener", listener);
1946
1947 synchronized (this) {
1948 if (characterDataListeners_ == null) {
1949 characterDataListeners_ = new ArrayList<>();
1950 }
1951 characterDataListeners_.add(listener);
1952
1953 final SgmlPage page = getPage();
1954 if (page != null) {
1955 page.characterDataChangeListenerAdded();
1956 }
1957 }
1958 }
1959
1960
1961
1962
1963
1964
1965
1966
1967 public void removeCharacterDataChangeListener(final CharacterDataChangeListener listener) {
1968 WebAssert.notNull("listener", listener);
1969
1970 synchronized (this) {
1971 if (characterDataListeners_ != null) {
1972 characterDataListeners_.remove(listener);
1973 }
1974 }
1975 }
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985 protected void fireCharacterDataChanged(final DomCharacterData characterData, final String oldValue) {
1986 CharacterDataChangeEvent event = null;
1987
1988 DomNode toInform = this;
1989 while (toInform != null) {
1990 if (toInform.characterDataListeners_ != null) {
1991 final List<CharacterDataChangeListener> listeners;
1992 synchronized (toInform) {
1993 listeners = new ArrayList<>(toInform.characterDataListeners_);
1994 }
1995
1996 if (event == null) {
1997 event = new CharacterDataChangeEvent(characterData, oldValue);
1998 }
1999 for (final CharacterDataChangeListener domChangeListener : listeners) {
2000 domChangeListener.characterDataChanged(event);
2001 }
2002 }
2003
2004 toInform = toInform.getParentNode();
2005 }
2006 }
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017 protected void fireNodeDeleted(final DomNode parentNode, final DomNode deletedNode) {
2018 DomChangeEvent event = null;
2019
2020 DomNode toInform = this;
2021 while (toInform != null) {
2022 if (toInform.domListeners_ != null) {
2023 final List<DomChangeListener> listeners;
2024 synchronized (toInform) {
2025 listeners = new ArrayList<>(toInform.domListeners_);
2026 }
2027
2028 if (event == null) {
2029 event = new DomChangeEvent(parentNode, deletedNode);
2030 }
2031 for (final DomChangeListener domChangeListener : listeners) {
2032 domChangeListener.nodeDeleted(event);
2033 }
2034 }
2035
2036 toInform = toInform.getParentNode();
2037 }
2038 }
2039
2040
2041
2042
2043
2044
2045
2046 public DomNodeList<DomNode> querySelectorAll(final String selectors) {
2047 try {
2048 final WebClient webClient = getPage().getWebClient();
2049 final SelectorList selectorList = getSelectorList(selectors, webClient);
2050
2051 final List<DomNode> elements = new ArrayList<>();
2052 if (selectorList != null) {
2053 for (final DomElement child : getDomElementDescendants()) {
2054 for (final Selector selector : selectorList) {
2055 if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, child, null, true, true)) {
2056 elements.add(child);
2057 break;
2058 }
2059 }
2060 }
2061 }
2062 return new StaticDomNodeList(elements);
2063 }
2064 catch (final IOException e) {
2065 throw new CSSException("Error parsing CSS selectors from '" + selectors + "': " + e.getMessage(), e);
2066 }
2067 }
2068
2069
2070
2071
2072
2073
2074
2075
2076 protected SelectorList getSelectorList(final String selectors, final WebClient webClient)
2077 throws IOException {
2078
2079
2080 try (PooledCSS3Parser pooledParser = webClient.getCSS3Parser()) {
2081 final CSSOMParser parser = new CSSOMParser(pooledParser);
2082 final CheckErrorHandler errorHandler = new CheckErrorHandler();
2083 parser.setErrorHandler(errorHandler);
2084
2085 final SelectorList selectorList = parser.parseSelectors(selectors);
2086
2087 if (errorHandler.error() != null) {
2088 throw new CSSException("Invalid selectors: '" + selectors + "'", errorHandler.error());
2089 }
2090
2091 if (selectorList != null) {
2092 CssStyleSheet.validateSelectors(selectorList, this);
2093
2094 }
2095 return selectorList;
2096 }
2097 }
2098
2099
2100
2101
2102
2103
2104
2105 @SuppressWarnings("unchecked")
2106 public <N extends DomNode> N querySelector(final String selectors) {
2107 final DomNodeList<DomNode> list = querySelectorAll(selectors);
2108 if (!list.isEmpty()) {
2109 return (N) list.get(0);
2110 }
2111 return null;
2112 }
2113
2114
2115
2116
2117
2118
2119
2120 public boolean isAttachedToPage() {
2121 return attachedToPage_;
2122 }
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133 public void processImportNode(final org.htmlunit.javascript.host.dom.Document doc) {
2134 page_ = (SgmlPage) doc.getDomNodeOrDie();
2135 }
2136
2137
2138
2139
2140
2141
2142
2143
2144 public boolean hasFeature(final BrowserVersionFeatures feature) {
2145 return getPage().getWebClient().getBrowserVersion().hasFeature(feature);
2146 }
2147
2148 private static final class CheckErrorHandler implements CSSErrorHandler {
2149 private CSSParseException error_;
2150
2151 CSSParseException error() {
2152 return error_;
2153 }
2154
2155 @Override
2156 public void warning(final CSSParseException exception) throws CSSException {
2157
2158 }
2159
2160 @Override
2161 public void fatalError(final CSSParseException exception) throws CSSException {
2162 error_ = exception;
2163 }
2164
2165 @Override
2166 public void error(final CSSParseException exception) throws CSSException {
2167 error_ = exception;
2168 }
2169 }
2170
2171
2172
2173
2174
2175
2176
2177 public boolean handles(final Event event) {
2178 return true;
2179 }
2180
2181
2182
2183
2184
2185
2186
2187 public DomElement getPreviousElementSibling() {
2188 DomNode node = getPreviousSibling();
2189 while (node != null && !(node instanceof DomElement)) {
2190 node = node.getPreviousSibling();
2191 }
2192 return (DomElement) node;
2193 }
2194
2195
2196
2197
2198
2199
2200
2201 public DomElement getNextElementSibling() {
2202 DomNode node = getNextSibling();
2203 while (node != null && !(node instanceof DomElement)) {
2204 node = node.getNextSibling();
2205 }
2206 return (DomElement) node;
2207 }
2208
2209
2210
2211
2212
2213 public DomElement closest(final String selectorString) {
2214 try {
2215 final WebClient webClient = getPage().getWebClient();
2216 final SelectorList selectorList = getSelectorList(selectorString, webClient);
2217
2218 DomNode current = this;
2219 if (selectorList != null) {
2220 do {
2221 for (final Selector selector : selectorList) {
2222 final DomElement elem = (DomElement) current;
2223 if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, elem, null, true, true)) {
2224 return elem;
2225 }
2226 }
2227
2228 do {
2229 current = current.getParentNode();
2230 }
2231 while (current != null && !(current instanceof DomElement));
2232 }
2233 while (current != null);
2234 }
2235 return null;
2236 }
2237 catch (final IOException e) {
2238 throw new CSSException("Error parsing CSS selectors from '" + selectorString + "': " + e.getMessage(), e);
2239 }
2240 }
2241
2242
2243
2244
2245 private static final class ReadOnlyEmptyNamedNodeMapImpl implements NamedNodeMap, Serializable {
2246
2247
2248
2249
2250 @Override
2251 public int getLength() {
2252 return 0;
2253 }
2254
2255
2256
2257
2258 @Override
2259 public DomAttr getNamedItem(final String name) {
2260 return null;
2261 }
2262
2263
2264
2265
2266 @Override
2267 public Node getNamedItemNS(final String namespaceURI, final String localName) {
2268 return null;
2269 }
2270
2271
2272
2273
2274 @Override
2275 public Node item(final int index) {
2276 return null;
2277 }
2278
2279
2280
2281
2282 @Override
2283 public Node removeNamedItem(final String name) throws DOMException {
2284 return null;
2285 }
2286
2287
2288
2289
2290 @Override
2291 public Node removeNamedItemNS(final String namespaceURI, final String localName) {
2292 return null;
2293 }
2294
2295
2296
2297
2298 @Override
2299 public DomAttr setNamedItem(final Node node) {
2300 throw new UnsupportedOperationException("ReadOnlyEmptyNamedAttrNodeMapImpl.setNamedItem");
2301 }
2302
2303
2304
2305
2306 @Override
2307 public Node setNamedItemNS(final Node node) throws DOMException {
2308 throw new UnsupportedOperationException("ReadOnlyEmptyNamedAttrNodeMapImpl.setNamedItemNS");
2309 }
2310 }
2311 }