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