1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.html;
16
17 import static org.htmlunit.BrowserVersionFeatures.EVENT_CONTEXT_MENU_HAS_DETAIL_1;
18 import static org.htmlunit.BrowserVersionFeatures.EVENT_ONCLICK_USES_POINTEREVENT;
19 import static org.htmlunit.BrowserVersionFeatures.JS_AREA_WITHOUT_HREF_FOCUSABLE;
20
21 import java.io.IOException;
22 import java.io.PrintWriter;
23 import java.io.Serializable;
24 import java.io.StringWriter;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Comparator;
28 import java.util.Iterator;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.NoSuchElementException;
34 import java.util.Set;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.htmlunit.BrowserVersion;
39 import org.htmlunit.Page;
40 import org.htmlunit.ScriptResult;
41 import org.htmlunit.SgmlPage;
42 import org.htmlunit.WebClient;
43 import org.htmlunit.css.ComputedCssStyleDeclaration;
44 import org.htmlunit.css.CssStyleSheet;
45 import org.htmlunit.css.StyleElement;
46 import org.htmlunit.cssparser.dom.CSSStyleDeclarationImpl;
47 import org.htmlunit.cssparser.dom.Property;
48 import org.htmlunit.cssparser.parser.CSSException;
49 import org.htmlunit.cssparser.parser.selector.Selector;
50 import org.htmlunit.cssparser.parser.selector.SelectorList;
51 import org.htmlunit.cssparser.parser.selector.SelectorSpecificity;
52 import org.htmlunit.cyberneko.util.FastHashMap;
53 import org.htmlunit.html.DefaultElementFactory.OrderedFastHashMapWithLowercaseKeys;
54 import org.htmlunit.javascript.AbstractJavaScriptEngine;
55 import org.htmlunit.javascript.JavaScriptEngine;
56 import org.htmlunit.javascript.host.event.Event;
57 import org.htmlunit.javascript.host.event.EventTarget;
58 import org.htmlunit.javascript.host.event.MouseEvent;
59 import org.htmlunit.javascript.host.event.PointerEvent;
60 import org.htmlunit.util.OrderedFastHashMap;
61 import org.htmlunit.util.StringUtils;
62 import org.w3c.dom.Attr;
63 import org.w3c.dom.DOMException;
64 import org.w3c.dom.Element;
65 import org.w3c.dom.NamedNodeMap;
66 import org.w3c.dom.Node;
67 import org.w3c.dom.TypeInfo;
68 import org.xml.sax.SAXException;
69
70
71
72
73
74
75
76
77
78 public class DomElement extends DomNamespaceNode implements Element {
79
80 private static final Log LOG = LogFactory.getLog(DomElement.class);
81
82
83 public static final String ID_ATTRIBUTE = "id";
84
85
86 public static final String NAME_ATTRIBUTE = "name";
87
88
89 public static final String SRC_ATTRIBUTE = "src";
90
91
92 public static final String VALUE_ATTRIBUTE = "value";
93
94
95 public static final String TYPE_ATTRIBUTE = "type";
96
97
98 public static final String ATTRIBUTE_NOT_DEFINED = new String("");
99
100
101 public static final String ATTRIBUTE_VALUE_EMPTY = new String();
102
103
104 private NamedAttrNodeMapImpl attributes_;
105
106
107 private FastHashMap<String, String> namespaces_;
108
109
110 private String styleString_;
111 private LinkedHashMap<String, StyleElement> styleMap_;
112
113 private static final Comparator<StyleElement> STYLE_ELEMENT_COMPARATOR =
114 (first, second) -> StyleElement.compareToByImportanceAndSpecificity(first, second);
115
116
117
118
119 private boolean mouseOver_;
120
121
122
123
124
125
126
127
128
129
130 public DomElement(final String namespaceURI, final String qualifiedName, final SgmlPage page,
131 final Map<String, DomAttr> attributes) {
132 super(namespaceURI, qualifiedName, page);
133
134 if (attributes == null) {
135 attributes_ = new NamedAttrNodeMapImpl(this, isAttributeCaseSensitive());
136 }
137 else {
138 attributes_ = new NamedAttrNodeMapImpl(this, isAttributeCaseSensitive(), attributes);
139
140 for (final DomAttr entry : attributes.values()) {
141 entry.setParentNode(this);
142 final String attrNamespaceURI = entry.getNamespaceURI();
143 final String prefix = entry.getPrefix();
144
145 if (attrNamespaceURI != null && prefix != null) {
146 if (namespaces_ == null) {
147 namespaces_ = new FastHashMap<>(1, 0.5f);
148 }
149 namespaces_.put(attrNamespaceURI, prefix);
150 }
151 }
152 }
153 }
154
155
156
157
158 @Override
159 public String getNodeName() {
160 return getQualifiedName();
161 }
162
163
164
165
166 @Override
167 public final short getNodeType() {
168 return ELEMENT_NODE;
169 }
170
171
172
173
174
175 @Override
176 public final String getTagName() {
177 return getNodeName();
178 }
179
180
181
182
183 @Override
184 public final boolean hasAttributes() {
185 return !attributes_.isEmpty();
186 }
187
188
189
190
191
192
193
194
195 @Override
196 public boolean hasAttribute(final String attributeName) {
197 return attributes_.containsKey(attributeName);
198 }
199
200
201
202
203
204
205
206
207
208
209
210 public void replaceStyleAttribute(final String name, final String value, final String priority) {
211 if (StringUtils.isBlank(value)) {
212 removeStyleAttribute(name);
213 return;
214 }
215
216 final Map<String, StyleElement> styleMap = getStyleMap();
217 final StyleElement old = styleMap.get(name);
218 final StyleElement element;
219 if (old == null) {
220 element = new StyleElement(name, value, priority, SelectorSpecificity.FROM_STYLE_ATTRIBUTE);
221 }
222 else {
223 element = new StyleElement(name, value, priority,
224 SelectorSpecificity.FROM_STYLE_ATTRIBUTE, old.getIndex());
225 }
226 styleMap.put(name, element);
227 writeStyleToElement(styleMap);
228 }
229
230
231
232
233
234
235
236
237 public String removeStyleAttribute(final String name) {
238 final Map<String, StyleElement> styleMap = getStyleMap();
239 final StyleElement value = styleMap.get(name);
240 if (value == null) {
241 return "";
242 }
243 styleMap.remove(name);
244 writeStyleToElement(styleMap);
245 return value.getValue();
246 }
247
248
249
250
251
252
253
254
255
256 public StyleElement getStyleElement(final String name) {
257 final Map<String, StyleElement> map = getStyleMap();
258 if (map != null) {
259 return map.get(name);
260 }
261 return null;
262 }
263
264
265
266
267
268
269
270
271
272
273 public StyleElement getStyleElementCaseInSensitive(final String name) {
274 final Map<String, StyleElement> map = getStyleMap();
275 for (final Map.Entry<String, StyleElement> entry : map.entrySet()) {
276 if (entry.getKey().equalsIgnoreCase(name)) {
277 return entry.getValue();
278 }
279 }
280 return null;
281 }
282
283
284
285
286
287
288
289
290
291 public LinkedHashMap<String, StyleElement> getStyleMap() {
292 final String styleAttribute = getAttributeDirect("style");
293 if (styleString_ == styleAttribute) {
294 return styleMap_;
295 }
296
297 final LinkedHashMap<String, StyleElement> styleMap = new LinkedHashMap<>();
298 if (ATTRIBUTE_NOT_DEFINED == styleAttribute || ATTRIBUTE_VALUE_EMPTY == styleAttribute) {
299 styleMap_ = styleMap;
300 styleString_ = styleAttribute;
301 return styleMap_;
302 }
303
304 final CSSStyleDeclarationImpl cssStyle = new CSSStyleDeclarationImpl(null);
305 try {
306
307
308 cssStyle.setCssText(styleAttribute, getPage().getWebClient().getCssErrorHandler());
309 }
310 catch (final Exception e) {
311 if (LOG.isErrorEnabled()) {
312 LOG.error("Error while parsing style value '" + styleAttribute + "'", e);
313 }
314 }
315
316 for (final Property prop : cssStyle.getProperties()) {
317 final String key = prop.getName().toLowerCase(Locale.ROOT);
318 final StyleElement element = new StyleElement(key,
319 prop.getValue().getCssText(),
320 prop.isImportant() ? StyleElement.PRIORITY_IMPORTANT : "",
321 SelectorSpecificity.FROM_STYLE_ATTRIBUTE);
322 styleMap.put(key, element);
323 }
324
325 styleMap_ = styleMap;
326 styleString_ = styleAttribute;
327
328 return styleMap_;
329 }
330
331
332
333
334
335
336 protected void printOpeningTagContentAsXml(final PrintWriter printWriter) {
337 printWriter.print(getTagName());
338 for (final Map.Entry<String, DomAttr> entry : attributes_.entrySet()) {
339 printWriter.print(" ");
340 printWriter.print(entry.getKey());
341 printWriter.print("=\"");
342 printWriter.print(StringUtils.escapeXmlAttributeValue(entry.getValue().getNodeValue()));
343 printWriter.print("\"");
344 }
345 }
346
347
348
349
350 @Override
351 protected boolean printXml(final String indent, final boolean tagBefore, final PrintWriter printWriter) {
352 final boolean hasChildren = getFirstChild() != null;
353
354 if (tagBefore) {
355 printWriter.print("\r\n");
356 printWriter.print(indent);
357 }
358
359 printWriter.print('<');
360 printOpeningTagContentAsXml(printWriter);
361
362 if (hasChildren) {
363 printWriter.print(">");
364 final boolean tag = printChildrenAsXml(indent, true, printWriter);
365 if (tag) {
366 printWriter.print("\r\n");
367 printWriter.print(indent);
368 }
369 printWriter.print("</");
370 printWriter.print(getTagName());
371 printWriter.print(">");
372 }
373 else if (isEmptyXmlTagExpanded()) {
374 printWriter.print("></");
375 printWriter.print(getTagName());
376 printWriter.print(">");
377 }
378 else {
379 printWriter.print("/>");
380 }
381
382 return true;
383 }
384
385
386
387
388
389
390 protected boolean isEmptyXmlTagExpanded() {
391 return false;
392 }
393
394
395
396
397
398
399
400
401
402 String getQualifiedName(final String namespaceURI, final String localName) {
403 final String qualifiedName;
404 if (namespaceURI == null) {
405 qualifiedName = localName;
406 }
407 else {
408 final String prefix = namespaces_ == null ? null : namespaces_.get(namespaceURI);
409 if (prefix == null) {
410 qualifiedName = null;
411 }
412 else {
413 qualifiedName = prefix + ':' + localName;
414 }
415 }
416 return qualifiedName;
417 }
418
419
420
421
422
423
424
425
426
427
428 @Override
429 public String getAttribute(final String attributeName) {
430 final DomAttr attr = attributes_.get(attributeName);
431 if (attr != null) {
432 return attr.getNodeValue();
433 }
434 return ATTRIBUTE_NOT_DEFINED;
435 }
436
437
438
439
440
441
442
443 public String getAttributeDirect(final String attributeName) {
444 final DomAttr attr = attributes_.getDirect(attributeName);
445 if (attr != null) {
446 return attr.getNodeValue();
447 }
448 return ATTRIBUTE_NOT_DEFINED;
449 }
450
451
452
453
454
455 @Override
456 public void removeAttribute(final String attributeName) {
457 attributes_.remove(attributeName);
458 }
459
460
461
462
463
464
465 @Override
466 public final void removeAttributeNS(final String namespaceURI, final String localName) {
467 final String qualifiedName = getQualifiedName(namespaceURI, localName);
468 if (qualifiedName != null) {
469 removeAttribute(qualifiedName);
470 }
471 }
472
473
474
475
476
477 @Override
478 public final Attr removeAttributeNode(final Attr attribute) {
479 throw new UnsupportedOperationException("DomElement.removeAttributeNode is not yet implemented.");
480 }
481
482
483
484
485
486
487
488
489
490 @Override
491 public final boolean hasAttributeNS(final String namespaceURI, final String localName) {
492 final String qualifiedName = getQualifiedName(namespaceURI, localName);
493 if (qualifiedName != null) {
494 return attributes_.get(qualifiedName) != null;
495 }
496 return false;
497 }
498
499
500
501
502
503 public final Map<String, DomAttr> getAttributesMap() {
504 return attributes_;
505 }
506
507
508
509
510 @Override
511 public NamedNodeMap getAttributes() {
512 return attributes_;
513 }
514
515
516
517
518
519
520
521 @Override
522 public void setAttribute(final String attributeName, final String attributeValue) {
523 setAttributeNS(null, attributeName, attributeValue);
524 }
525
526
527
528
529
530
531
532
533 @Override
534 public void setAttributeNS(final String namespaceURI, final String qualifiedName,
535 final String attributeValue) {
536 setAttributeNS(namespaceURI, qualifiedName, attributeValue, true, true);
537 }
538
539
540
541
542
543
544
545
546
547
548 protected void setAttributeNS(final String namespaceURI, final String qualifiedName,
549 final String attributeValue, final boolean notifyAttributeChangeListeners,
550 final boolean notifyMutationObservers) {
551 final DomAttr newAttr = new DomAttr(getPage(), namespaceURI, qualifiedName, attributeValue, true);
552 newAttr.setParentNode(this);
553 attributes_.put(qualifiedName, newAttr);
554
555 if (namespaceURI != null) {
556 if (namespaces_ == null) {
557 namespaces_ = new FastHashMap<>(1, 0.5f);
558 }
559 namespaces_.put(namespaceURI, newAttr.getPrefix());
560 }
561 }
562
563
564
565
566
567 protected boolean isAttributeCaseSensitive() {
568 return true;
569 }
570
571
572
573
574
575
576
577
578
579
580
581 @Override
582 public final String getAttributeNS(final String namespaceURI, final String localName) {
583 final String qualifiedName = getQualifiedName(namespaceURI, localName);
584 if (qualifiedName != null) {
585 return getAttribute(qualifiedName);
586 }
587 return ATTRIBUTE_NOT_DEFINED;
588 }
589
590
591
592
593 @Override
594 public DomAttr getAttributeNode(final String name) {
595 return attributes_.get(name);
596 }
597
598
599
600
601 @Override
602 public DomAttr getAttributeNodeNS(final String namespaceURI, final String localName) {
603 final String qualifiedName = getQualifiedName(namespaceURI, localName);
604 if (qualifiedName != null) {
605 return attributes_.get(qualifiedName);
606 }
607 return null;
608 }
609
610
611
612
613
614
615 public void writeStyleToElement(final Map<String, StyleElement> styleMap) {
616 if (styleMap.isEmpty()) {
617 setAttribute("style", "");
618 return;
619 }
620
621 final StringBuilder builder = new StringBuilder();
622 final List<StyleElement> styleElements = new ArrayList<>(styleMap.values());
623 styleElements.sort(STYLE_ELEMENT_COMPARATOR);
624 for (final StyleElement e : styleElements) {
625 if (builder.length() != 0) {
626 builder.append(' ');
627 }
628 builder.append(e.getName())
629 .append(": ")
630 .append(e.getValue());
631
632 final String prio = e.getPriority();
633 if (StringUtils.isNotBlank(prio)) {
634 builder.append(" !").append(prio);
635 }
636 builder.append(';');
637 }
638 setAttribute("style", builder.toString());
639 }
640
641
642
643
644 @Override
645 public DomNodeList<HtmlElement> getElementsByTagName(final String tagName) {
646 return getElementsByTagNameImpl(tagName);
647 }
648
649
650
651
652
653
654 <E extends HtmlElement> DomNodeList<E> getElementsByTagNameImpl(final String tagName) {
655 return new AbstractDomNodeList<E>(this) {
656 @Override
657 @SuppressWarnings("unchecked")
658 protected List<E> provideElements() {
659 final List<E> res = new ArrayList<>();
660 for (final HtmlElement elem : getDomNode().getHtmlElementDescendants()) {
661 if (elem.getLocalName().equalsIgnoreCase(tagName)) {
662 res.add((E) elem);
663 }
664 }
665 return res;
666 }
667 };
668 }
669
670
671
672
673
674
675
676
677 public <E extends HtmlElement> List<E> getStaticElementsByTagName(final String tagName) {
678 final List<E> res = new ArrayList<>();
679 for (final Iterator<HtmlElement> iterator = this.new DescendantHtmlElementsIterator(); iterator.hasNext();) {
680 final HtmlElement elem = iterator.next();
681 if (elem.getLocalName().equalsIgnoreCase(tagName)) {
682 final String prefix = elem.getPrefix();
683 if (prefix == null || prefix.isEmpty()) {
684 res.add((E) elem);
685 }
686 }
687 }
688 return res;
689 }
690
691
692
693
694
695 @Override
696 public DomNodeList<HtmlElement> getElementsByTagNameNS(final String namespace, final String localName) {
697 throw new UnsupportedOperationException("DomElement.getElementsByTagNameNS is not yet implemented.");
698 }
699
700
701
702
703
704 @Override
705 public TypeInfo getSchemaTypeInfo() {
706 throw new UnsupportedOperationException("DomElement.getSchemaTypeInfo is not yet implemented.");
707 }
708
709
710
711
712
713 @Override
714 public void setIdAttribute(final String name, final boolean isId) {
715 throw new UnsupportedOperationException("DomElement.setIdAttribute is not yet implemented.");
716 }
717
718
719
720
721
722 @Override
723 public void setIdAttributeNS(final String namespaceURI, final String localName, final boolean isId) {
724 throw new UnsupportedOperationException("DomElement.setIdAttributeNS is not yet implemented.");
725 }
726
727
728
729
730 @Override
731 public Attr setAttributeNode(final Attr attribute) {
732 attributes_.setNamedItem(attribute);
733 return null;
734 }
735
736
737
738
739
740 @Override
741 public Attr setAttributeNodeNS(final Attr attribute) {
742 throw new UnsupportedOperationException("DomElement.setAttributeNodeNS is not yet implemented.");
743 }
744
745
746
747
748
749 @Override
750 public final void setIdAttributeNode(final Attr idAttr, final boolean isId) {
751 throw new UnsupportedOperationException("DomElement.setIdAttributeNode is not yet implemented.");
752 }
753
754
755
756
757 @Override
758 public DomNode cloneNode(final boolean deep) {
759 final DomElement clone = (DomElement) super.cloneNode(deep);
760 clone.attributes_ = new NamedAttrNodeMapImpl(clone, isAttributeCaseSensitive());
761 clone.attributes_.putAll(attributes_);
762 return clone;
763 }
764
765
766
767
768 public final String getId() {
769 return getAttributeDirect(ID_ATTRIBUTE);
770 }
771
772
773
774
775
776
777 public final void setId(final String newId) {
778 setAttribute(ID_ATTRIBUTE, newId);
779 }
780
781
782
783
784
785 public DomElement getFirstElementChild() {
786 final Iterator<DomElement> i = getChildElements().iterator();
787 if (i.hasNext()) {
788 return i.next();
789 }
790 return null;
791 }
792
793
794
795
796
797 public DomElement getLastElementChild() {
798 DomElement lastChild = null;
799 for (final DomElement domElement : getChildElements()) {
800 lastChild = domElement;
801 }
802 return lastChild;
803 }
804
805
806
807
808
809 public int getChildElementCount() {
810 int counter = 0;
811
812 for (final DomElement domElement : getChildElements()) {
813 counter++;
814 }
815 return counter;
816 }
817
818
819
820
821 public final Iterable<DomElement> getChildElements() {
822 return new ChildElementsIterable(this);
823 }
824
825
826
827
828 private static class ChildElementsIterable implements Iterable<DomElement> {
829 private final Iterator<DomElement> iterator_;
830
831
832
833
834
835 protected ChildElementsIterable(final DomNode domNode) {
836 iterator_ = new ChildElementsIterator(domNode);
837 }
838
839 @Override
840 public Iterator<DomElement> iterator() {
841 return iterator_;
842 }
843 }
844
845
846
847
848 protected static class ChildElementsIterator implements Iterator<DomElement> {
849
850 private DomElement nextElement_;
851
852
853
854
855
856 protected ChildElementsIterator(final DomNode domNode) {
857 final DomNode child = domNode.getFirstChild();
858 if (child != null) {
859 if (child instanceof DomElement) {
860 nextElement_ = (DomElement) child;
861 }
862 else {
863 setNextElement(child);
864 }
865 }
866 }
867
868
869
870
871 @Override
872 public boolean hasNext() {
873 return nextElement_ != null;
874 }
875
876
877
878
879 @Override
880 public DomElement next() {
881 if (nextElement_ != null) {
882 final DomElement result = nextElement_;
883 setNextElement(nextElement_);
884 return result;
885 }
886 throw new NoSuchElementException();
887 }
888
889
890 @Override
891 public void remove() {
892 if (nextElement_ == null) {
893 throw new IllegalStateException();
894 }
895 final DomNode sibling = nextElement_.getPreviousSibling();
896 if (sibling != null) {
897 sibling.remove();
898 }
899 }
900
901 private void setNextElement(final DomNode node) {
902 DomNode next = node.getNextSibling();
903 while (next != null && !(next instanceof DomElement)) {
904 next = next.getNextSibling();
905 }
906 nextElement_ = (DomElement) next;
907 }
908 }
909
910
911
912
913
914 @Override
915 public String toString() {
916 final StringWriter writer = new StringWriter();
917 final PrintWriter printWriter = new PrintWriter(writer);
918
919 printWriter.print(getClass().getSimpleName());
920 printWriter.print("[<");
921 printOpeningTagContentAsXml(printWriter);
922 printWriter.print(">]");
923 printWriter.flush();
924 return writer.toString();
925 }
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941 public <P extends Page> P click() throws IOException {
942 return click(false, false, false);
943 }
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962 public <P extends Page> P click(final boolean shiftKey, final boolean ctrlKey, final boolean altKey)
963 throws IOException {
964
965 return click(shiftKey, ctrlKey, altKey, true);
966 }
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986 public <P extends Page> P click(final boolean shiftKey, final boolean ctrlKey, final boolean altKey,
987 final boolean triggerMouseEvents) throws IOException {
988 return click(shiftKey, ctrlKey, altKey, triggerMouseEvents, true, false, false);
989 }
990
991
992
993
994 protected boolean isDisabledElementAndDisabled() {
995 return this instanceof DisabledElement && ((DisabledElement) this).isDisabled();
996 }
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017 @SuppressWarnings("unchecked")
1018 public <P extends Page> P click(final boolean shiftKey, final boolean ctrlKey, final boolean altKey,
1019 final boolean triggerMouseEvents, final boolean handleFocus, final boolean ignoreVisibility,
1020 final boolean disableProcessLabelAfterBubbling) throws IOException {
1021
1022
1023 final SgmlPage page = getPage();
1024 final WebClient webClient = page.getWebClient();
1025 webClient.setCurrentWindow(page.getEnclosingWindow());
1026
1027 if (!ignoreVisibility) {
1028 if (!(page instanceof HtmlPage)) {
1029 return (P) page;
1030 }
1031
1032 if (!isDisplayed()) {
1033 if (LOG.isWarnEnabled()) {
1034 LOG.warn("Calling click() ignored because the target element '" + this
1035 + "' is not displayed.");
1036 }
1037 return (P) page;
1038 }
1039
1040 if (isDisabledElementAndDisabled()) {
1041 if (LOG.isWarnEnabled()) {
1042 LOG.warn("Calling click() ignored because the target element '" + this + "' is disabled.");
1043 }
1044 return (P) page;
1045 }
1046 }
1047
1048 synchronized (page) {
1049 if (triggerMouseEvents) {
1050 mouseDown(shiftKey, ctrlKey, altKey, MouseEvent.BUTTON_LEFT);
1051 }
1052
1053 final AbstractJavaScriptEngine<?> jsEngine = webClient.getJavaScriptEngine();
1054 if (webClient.isJavaScriptEnabled()) {
1055 jsEngine.holdPosponedActions();
1056 }
1057 try {
1058 if (handleFocus) {
1059
1060 DomElement elementToFocus = null;
1061 if (this instanceof SubmittableElement
1062 || this instanceof HtmlAnchor
1063 && ATTRIBUTE_NOT_DEFINED != ((HtmlAnchor) this).getHrefAttribute()
1064 || this instanceof HtmlArea
1065 && (ATTRIBUTE_NOT_DEFINED != ((HtmlArea) this).getHrefAttribute()
1066 || webClient.getBrowserVersion().hasFeature(JS_AREA_WITHOUT_HREF_FOCUSABLE))
1067 || this instanceof HtmlElement && ((HtmlElement) this).getTabIndex() != null) {
1068 elementToFocus = this;
1069 }
1070 else if (this instanceof HtmlOption) {
1071 elementToFocus = ((HtmlOption) this).getEnclosingSelect();
1072 }
1073
1074 if (elementToFocus == null) {
1075 ((HtmlPage) page).setFocusedElement(null);
1076 }
1077 else {
1078 elementToFocus.focus();
1079 }
1080 }
1081
1082 if (triggerMouseEvents) {
1083 mouseUp(shiftKey, ctrlKey, altKey, MouseEvent.BUTTON_LEFT);
1084 }
1085
1086 MouseEvent event = null;
1087 if (webClient.isJavaScriptEnabled()) {
1088 final BrowserVersion browser = webClient.getBrowserVersion();
1089 if (browser.hasFeature(EVENT_ONCLICK_USES_POINTEREVENT)) {
1090 event = new PointerEvent(getEventTargetElement(), MouseEvent.TYPE_CLICK, shiftKey,
1091 ctrlKey, altKey, MouseEvent.BUTTON_LEFT, 1);
1092 }
1093 else {
1094 event = new MouseEvent(getEventTargetElement(), MouseEvent.TYPE_CLICK, shiftKey,
1095 ctrlKey, altKey, MouseEvent.BUTTON_LEFT, 1);
1096 }
1097
1098 if (disableProcessLabelAfterBubbling) {
1099 event.disableProcessLabelAfterBubbling();
1100 }
1101 }
1102 click(event, shiftKey, ctrlKey, altKey, ignoreVisibility);
1103 }
1104 finally {
1105 if (webClient.isJavaScriptEnabled()) {
1106 jsEngine.processPostponedActions();
1107 }
1108 }
1109
1110 return (P) webClient.getCurrentWindow().getEnclosedPage();
1111 }
1112 }
1113
1114
1115
1116
1117
1118
1119 protected DomNode getEventTargetElement() {
1120 return this;
1121 }
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140 @SuppressWarnings("unchecked")
1141 public <P extends Page> P click(final Event event,
1142 final boolean shiftKey, final boolean ctrlKey, final boolean altKey,
1143 final boolean ignoreVisibility) throws IOException {
1144 final SgmlPage page = getPage();
1145
1146 if ((!ignoreVisibility && !isDisplayed()) || isDisabledElementAndDisabled()) {
1147 return (P) page;
1148 }
1149
1150 final WebClient webClient = page.getWebClient();
1151 if (!webClient.isJavaScriptEnabled()) {
1152 doClickStateUpdate(shiftKey, ctrlKey);
1153
1154 webClient.loadDownloadedResponses();
1155 return (P) getPage().getWebClient().getCurrentWindow().getEnclosedPage();
1156 }
1157
1158
1159
1160 final Page contentPage = page.getEnclosingWindow().getEnclosedPage();
1161
1162 boolean stateUpdated = false;
1163 boolean changed = false;
1164 if (isStateUpdateFirst()) {
1165 changed = doClickStateUpdate(shiftKey, ctrlKey);
1166 stateUpdated = true;
1167 }
1168
1169 final ScriptResult scriptResult = doClickFireClickEvent(event);
1170 final boolean eventIsAborted = event.isAborted(scriptResult);
1171
1172 final boolean pageAlreadyChanged = contentPage != page.getEnclosingWindow().getEnclosedPage();
1173 if (!pageAlreadyChanged && !stateUpdated && !eventIsAborted) {
1174 changed = doClickStateUpdate(shiftKey, ctrlKey);
1175 }
1176
1177 if (changed) {
1178 doClickFireChangeEvent();
1179 }
1180
1181 webClient.loadDownloadedResponses();
1182 return (P) getPage().getWebClient().getCurrentWindow().getEnclosedPage();
1183 }
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197 protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
1198 if (propagateClickStateUpdateToParent()) {
1199
1200
1201
1202 final DomNode parent = getParentNode();
1203 if (parent instanceof DomElement) {
1204 return ((DomElement) parent).doClickStateUpdate(false, false);
1205 }
1206 }
1207
1208 return false;
1209 }
1210
1211
1212
1213
1214
1215
1216 protected boolean propagateClickStateUpdateToParent() {
1217 return true;
1218 }
1219
1220
1221
1222
1223 protected void doClickFireChangeEvent() {
1224
1225 }
1226
1227
1228
1229
1230
1231
1232 protected ScriptResult doClickFireClickEvent(final Event event) {
1233 return fireEvent(event);
1234 }
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246 public <P extends Page> P dblClick() throws IOException {
1247 return dblClick(false, false, false);
1248 }
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264 @SuppressWarnings("unchecked")
1265 public <P extends Page> P dblClick(final boolean shiftKey, final boolean ctrlKey, final boolean altKey)
1266 throws IOException {
1267 if (isDisabledElementAndDisabled()) {
1268 return (P) getPage();
1269 }
1270
1271
1272 P clickPage = click(shiftKey, ctrlKey, altKey);
1273 if (clickPage != getPage()) {
1274 LOG.debug("dblClick() is ignored, as click() loaded a different page.");
1275 return clickPage;
1276 }
1277
1278
1279 clickPage = click(shiftKey, ctrlKey, altKey);
1280 if (clickPage != getPage()) {
1281 LOG.debug("dblClick() is ignored, as click() loaded a different page.");
1282 return clickPage;
1283 }
1284
1285 final Event event;
1286 event = new MouseEvent(this, MouseEvent.TYPE_DBL_CLICK, shiftKey, ctrlKey, altKey,
1287 MouseEvent.BUTTON_LEFT, 2);
1288
1289 final ScriptResult scriptResult = fireEvent(event);
1290 if (scriptResult == null) {
1291 return clickPage;
1292 }
1293 return (P) getPage().getWebClient().getCurrentWindow().getEnclosedPage();
1294 }
1295
1296
1297
1298
1299
1300
1301
1302
1303 public Page mouseOver() {
1304 return mouseOver(false, false, false, MouseEvent.BUTTON_LEFT);
1305 }
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319 public Page mouseOver(final boolean shiftKey, final boolean ctrlKey, final boolean altKey, final int button) {
1320 return doMouseEvent(MouseEvent.TYPE_MOUSE_OVER, shiftKey, ctrlKey, altKey, button);
1321 }
1322
1323
1324
1325
1326
1327
1328
1329
1330 public Page mouseMove() {
1331 return mouseMove(false, false, false, MouseEvent.BUTTON_LEFT);
1332 }
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346 public Page mouseMove(final boolean shiftKey, final boolean ctrlKey, final boolean altKey, final int button) {
1347 return doMouseEvent(MouseEvent.TYPE_MOUSE_MOVE, shiftKey, ctrlKey, altKey, button);
1348 }
1349
1350
1351
1352
1353
1354
1355
1356
1357 public Page mouseOut() {
1358 return mouseOut(false, false, false, MouseEvent.BUTTON_LEFT);
1359 }
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373 public Page mouseOut(final boolean shiftKey, final boolean ctrlKey, final boolean altKey, final int button) {
1374 return doMouseEvent(MouseEvent.TYPE_MOUSE_OUT, shiftKey, ctrlKey, altKey, button);
1375 }
1376
1377
1378
1379
1380
1381
1382
1383
1384 public Page mouseDown() {
1385 return mouseDown(false, false, false, MouseEvent.BUTTON_LEFT);
1386 }
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400 public Page mouseDown(final boolean shiftKey, final boolean ctrlKey, final boolean altKey, final int button) {
1401 return doMouseEvent(MouseEvent.TYPE_MOUSE_DOWN, shiftKey, ctrlKey, altKey, button);
1402 }
1403
1404
1405
1406
1407
1408
1409
1410
1411 public Page mouseUp() {
1412 return mouseUp(false, false, false, MouseEvent.BUTTON_LEFT);
1413 }
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427 public Page mouseUp(final boolean shiftKey, final boolean ctrlKey, final boolean altKey, final int button) {
1428 return doMouseEvent(MouseEvent.TYPE_MOUSE_UP, shiftKey, ctrlKey, altKey, button);
1429 }
1430
1431
1432
1433
1434
1435
1436
1437
1438 public Page rightClick() {
1439 return rightClick(false, false, false);
1440 }
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452 public Page rightClick(final boolean shiftKey, final boolean ctrlKey, final boolean altKey) {
1453 final Page mouseDownPage = mouseDown(shiftKey, ctrlKey, altKey, MouseEvent.BUTTON_RIGHT);
1454 if (mouseDownPage != getPage()) {
1455 LOG.debug("rightClick() is incomplete, as mouseDown() loaded a different page.");
1456 return mouseDownPage;
1457 }
1458
1459 final Page mouseUpPage = mouseUp(shiftKey, ctrlKey, altKey, MouseEvent.BUTTON_RIGHT);
1460 if (mouseUpPage != getPage()) {
1461 LOG.debug("rightClick() is incomplete, as mouseUp() loaded a different page.");
1462 return mouseUpPage;
1463 }
1464
1465 return doMouseEvent(MouseEvent.TYPE_CONTEXT_MENU, shiftKey, ctrlKey, altKey, MouseEvent.BUTTON_RIGHT);
1466 }
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480 private Page doMouseEvent(final String eventType, final boolean shiftKey, final boolean ctrlKey,
1481 final boolean altKey, final int button) {
1482 final SgmlPage page = getPage();
1483 final WebClient webClient = getPage().getWebClient();
1484 if (!webClient.isJavaScriptEnabled()) {
1485 return page;
1486 }
1487
1488 final ScriptResult scriptResult;
1489 final Event event;
1490 if (MouseEvent.TYPE_CONTEXT_MENU.equals(eventType)) {
1491 final BrowserVersion browserVersion = webClient.getBrowserVersion();
1492 if (browserVersion.hasFeature(EVENT_ONCLICK_USES_POINTEREVENT)) {
1493 if (browserVersion.hasFeature(EVENT_CONTEXT_MENU_HAS_DETAIL_1)) {
1494 event = new PointerEvent(this, eventType, shiftKey, ctrlKey, altKey, button, 1);
1495 }
1496 else {
1497 event = new PointerEvent(this, eventType, shiftKey, ctrlKey, altKey, button, 0);
1498 }
1499 }
1500 else {
1501 event = new MouseEvent(this, eventType, shiftKey, ctrlKey, altKey, button, 1);
1502 }
1503 }
1504 else if (MouseEvent.TYPE_DBL_CLICK.equals(eventType)) {
1505 event = new MouseEvent(this, eventType, shiftKey, ctrlKey, altKey, button, 2);
1506 }
1507 else {
1508 event = new MouseEvent(this, eventType, shiftKey, ctrlKey, altKey, button, 1);
1509 }
1510 scriptResult = fireEvent(event);
1511
1512 final Page currentPage;
1513 if (scriptResult == null) {
1514 currentPage = page;
1515 }
1516 else {
1517 currentPage = webClient.getCurrentWindow().getEnclosedPage();
1518 }
1519
1520 final boolean mouseOver = !MouseEvent.TYPE_MOUSE_OUT.equals(eventType);
1521 if (mouseOver_ != mouseOver) {
1522 mouseOver_ = mouseOver;
1523
1524 page.clearComputedStyles();
1525 }
1526
1527 return currentPage;
1528 }
1529
1530
1531
1532
1533
1534
1535
1536
1537 public ScriptResult fireEvent(final String eventType) {
1538 if (getPage().getWebClient().isJavaScriptEnabled()) {
1539 return fireEvent(new Event(this, eventType));
1540 }
1541 return null;
1542 }
1543
1544
1545
1546
1547
1548
1549
1550
1551 public ScriptResult fireEvent(final Event event) {
1552 final WebClient client = getPage().getWebClient();
1553 if (!client.isJavaScriptEnabled()) {
1554 return null;
1555 }
1556
1557 if (!handles(event)) {
1558 return null;
1559 }
1560
1561 if (LOG.isDebugEnabled()) {
1562 LOG.debug("Firing " + event);
1563 }
1564
1565 final EventTarget jsElt = getScriptableObject();
1566 final ScriptResult result = ((JavaScriptEngine) client.getJavaScriptEngine())
1567 .callSecured(cx -> jsElt.fireEvent(event), getHtmlPageOrNull());
1568 if (event.isAborted(result)) {
1569 preventDefault();
1570 }
1571 return result;
1572 }
1573
1574
1575
1576
1577
1578
1579 protected void preventDefault() {
1580
1581 }
1582
1583
1584
1585
1586 public void focus() {
1587 if (!(this instanceof SubmittableElement
1588 || this instanceof HtmlAnchor && ATTRIBUTE_NOT_DEFINED != ((HtmlAnchor) this).getHrefAttribute()
1589 || this instanceof HtmlArea
1590 && (ATTRIBUTE_NOT_DEFINED != ((HtmlArea) this).getHrefAttribute()
1591 || getPage().getWebClient().getBrowserVersion().hasFeature(JS_AREA_WITHOUT_HREF_FOCUSABLE))
1592 || this instanceof HtmlElement && ((HtmlElement) this).getTabIndex() != null)) {
1593 return;
1594 }
1595
1596 if (!isDisplayed() || isDisabledElementAndDisabled()) {
1597 return;
1598 }
1599
1600 final HtmlPage page = (HtmlPage) getPage();
1601 page.setFocusedElement(this);
1602 }
1603
1604
1605
1606
1607 public void blur() {
1608 final HtmlPage page = (HtmlPage) getPage();
1609 if (page.getFocusedElement() != this) {
1610 return;
1611 }
1612
1613 page.setFocusedElement(null);
1614 }
1615
1616
1617
1618
1619
1620
1621 public void removeFocus() {
1622
1623 }
1624
1625
1626
1627
1628
1629
1630
1631 protected boolean isStateUpdateFirst() {
1632 return false;
1633 }
1634
1635
1636
1637
1638
1639 public boolean isMouseOver() {
1640 if (mouseOver_) {
1641 return true;
1642 }
1643 for (final DomElement child : getChildElements()) {
1644 if (child.isMouseOver()) {
1645 return true;
1646 }
1647 }
1648 return false;
1649 }
1650
1651
1652
1653
1654
1655
1656 public boolean matches(final String selectorString) {
1657 try {
1658 final WebClient webClient = getPage().getWebClient();
1659 final SelectorList selectorList = getSelectorList(selectorString, webClient);
1660
1661 if (selectorList != null) {
1662 for (final Selector selector : selectorList) {
1663 if (CssStyleSheet.selects(webClient.getBrowserVersion(), selector, this, null, true, true)) {
1664 return true;
1665 }
1666 }
1667 }
1668 return false;
1669 }
1670 catch (final IOException e) {
1671 throw new CSSException("Error parsing CSS selectors from '" + selectorString + "': " + e.getMessage(), e);
1672 }
1673 }
1674
1675
1676
1677
1678 @Override
1679 public void setNodeValue(final String value) {
1680
1681 }
1682
1683
1684
1685
1686
1687
1688
1689
1690 public void setDefaults(final ComputedCssStyleDeclaration style) {
1691
1692 }
1693
1694
1695
1696
1697
1698
1699
1700 public void setInnerHtml(final String source) throws SAXException, IOException {
1701 removeAllChildren();
1702 getPage().clearComputedStylesUpToRoot(this);
1703
1704 if (source != null) {
1705 parseHtmlSnippet(source);
1706 }
1707 }
1708 }
1709
1710
1711
1712
1713 class NamedAttrNodeMapImpl implements Map<String, DomAttr>, NamedNodeMap, Serializable {
1714 private final OrderedFastHashMap<String, DomAttr> map_;
1715 private final DomElement domNode_;
1716 private final boolean caseSensitive_;
1717
1718 NamedAttrNodeMapImpl(final DomElement domNode, final boolean caseSensitive) {
1719 super();
1720 if (domNode == null) {
1721 throw new IllegalArgumentException("Provided domNode can't be null.");
1722 }
1723 domNode_ = domNode;
1724 caseSensitive_ = caseSensitive;
1725 map_ = new OrderedFastHashMap<>(0);
1726 }
1727
1728 NamedAttrNodeMapImpl(final DomElement domNode, final boolean caseSensitive,
1729 final Map<String, DomAttr> attributes) {
1730 super();
1731 if (domNode == null) {
1732 throw new IllegalArgumentException("Provided domNode can't be null.");
1733 }
1734 domNode_ = domNode;
1735 caseSensitive_ = caseSensitive;
1736
1737 if (attributes instanceof OrderedFastHashMapWithLowercaseKeys) {
1738
1739
1740 map_ = (OrderedFastHashMap) attributes;
1741 }
1742 else if (caseSensitive && attributes instanceof OrderedFastHashMap) {
1743
1744
1745 map_ = (OrderedFastHashMap) attributes;
1746 }
1747 else {
1748
1749 map_ = new OrderedFastHashMap<>(attributes.size());
1750
1751 putAll(attributes);
1752 }
1753 }
1754
1755
1756
1757
1758 @Override
1759 public int getLength() {
1760 return size();
1761 }
1762
1763
1764
1765
1766 @Override
1767 public DomAttr getNamedItem(final String name) {
1768 return get(name);
1769 }
1770
1771 private String fixName(final String name) {
1772 if (caseSensitive_) {
1773 return name;
1774 }
1775 return StringUtils.toRootLowerCase(name);
1776 }
1777
1778
1779
1780
1781 @Override
1782 public Node getNamedItemNS(final String namespaceURI, final String localName) {
1783 if (domNode_ == null) {
1784 return null;
1785 }
1786 return get(domNode_.getQualifiedName(namespaceURI, fixName(localName)));
1787 }
1788
1789
1790
1791
1792 @Override
1793 public Node item(final int index) {
1794 if (index < 0 || index >= map_.size()) {
1795 return null;
1796 }
1797 return map_.getValue(index);
1798 }
1799
1800
1801
1802
1803 @Override
1804 public Node removeNamedItem(final String name) throws DOMException {
1805 return remove(name);
1806 }
1807
1808
1809
1810
1811 @Override
1812 public Node removeNamedItemNS(final String namespaceURI, final String localName) {
1813 if (domNode_ == null) {
1814 return null;
1815 }
1816 return remove(domNode_.getQualifiedName(namespaceURI, fixName(localName)));
1817 }
1818
1819
1820
1821
1822 @Override
1823 public DomAttr setNamedItem(final Node node) {
1824 return put(node.getLocalName(), (DomAttr) node);
1825 }
1826
1827
1828
1829
1830 @Override
1831 public Node setNamedItemNS(final Node node) throws DOMException {
1832 return put(node.getNodeName(), (DomAttr) node);
1833 }
1834
1835
1836
1837
1838 @Override
1839 public DomAttr put(final String key, final DomAttr value) {
1840 final String name = fixName(key);
1841 return map_.put(name, value);
1842 }
1843
1844
1845
1846
1847 @Override
1848 public DomAttr remove(final Object key) {
1849 if (key instanceof String) {
1850 final String name = fixName((String) key);
1851 return map_.remove(name);
1852 }
1853 return null;
1854 }
1855
1856
1857
1858
1859 @Override
1860 public void clear() {
1861 map_.clear();
1862 }
1863
1864
1865
1866
1867 @Override
1868 public void putAll(final Map<? extends String, ? extends DomAttr> t) {
1869
1870 for (final Map.Entry<? extends String, ? extends DomAttr> entry : t.entrySet()) {
1871 put(entry.getKey(), entry.getValue());
1872 }
1873 }
1874
1875
1876
1877
1878 @Override
1879 public boolean containsKey(final Object key) {
1880 if (key instanceof String) {
1881 final String name = fixName((String) key);
1882 return map_.containsKey(name);
1883 }
1884 return false;
1885 }
1886
1887
1888
1889
1890 @Override
1891 public DomAttr get(final Object key) {
1892 if (key instanceof String) {
1893 final String name = fixName((String) key);
1894 return map_.get(name);
1895 }
1896 return null;
1897 }
1898
1899
1900
1901
1902
1903 protected DomAttr getDirect(final String key) {
1904 return map_.get(key);
1905 }
1906
1907
1908
1909
1910 @Override
1911 public boolean containsValue(final Object value) {
1912 return map_.containsValue(value);
1913 }
1914
1915
1916
1917
1918 @Override
1919 public Set<Map.Entry<String, DomAttr>> entrySet() {
1920 return map_.entrySet();
1921 }
1922
1923
1924
1925
1926 @Override
1927 public boolean isEmpty() {
1928 return map_.isEmpty();
1929 }
1930
1931
1932
1933
1934 @Override
1935 public Set<String> keySet() {
1936 return map_.keySet();
1937 }
1938
1939
1940
1941
1942 @Override
1943 public int size() {
1944 return map_.size();
1945 }
1946
1947
1948
1949
1950 @Override
1951 public Collection<DomAttr> values() {
1952 return map_.values();
1953 }
1954 }