1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host;
16
17 import static org.htmlunit.BrowserVersionFeatures.EVENT_SCROLL_UIEVENT;
18 import static org.htmlunit.html.DomElement.ATTRIBUTE_NOT_DEFINED;
19 import static org.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
20 import static org.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
21 import static org.htmlunit.javascript.configuration.SupportedBrowser.FF;
22 import static org.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
23
24 import java.io.IOException;
25 import java.io.Serializable;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.function.Predicate;
31 import java.util.regex.Pattern;
32
33 import org.apache.commons.logging.LogFactory;
34 import org.htmlunit.SgmlPage;
35 import org.htmlunit.corejs.javascript.BaseFunction;
36 import org.htmlunit.corejs.javascript.Context;
37 import org.htmlunit.corejs.javascript.Function;
38 import org.htmlunit.corejs.javascript.NativeObject;
39 import org.htmlunit.corejs.javascript.Scriptable;
40 import org.htmlunit.corejs.javascript.ScriptableObject;
41 import org.htmlunit.corejs.javascript.TopLevel;
42 import org.htmlunit.corejs.javascript.VarScope;
43 import org.htmlunit.corejs.javascript.WithScope;
44 import org.htmlunit.css.ComputedCssStyleDeclaration;
45 import org.htmlunit.css.ElementCssStyleDeclaration;
46 import org.htmlunit.cssparser.parser.CSSException;
47 import org.htmlunit.html.DomAttr;
48 import org.htmlunit.html.DomCDataSection;
49 import org.htmlunit.html.DomCharacterData;
50 import org.htmlunit.html.DomComment;
51 import org.htmlunit.html.DomElement;
52 import org.htmlunit.html.DomNode;
53 import org.htmlunit.html.DomText;
54 import org.htmlunit.html.HtmlElement;
55 import org.htmlunit.html.HtmlElement.DisplayStyle;
56 import org.htmlunit.html.HtmlTemplate;
57 import org.htmlunit.javascript.HtmlUnitScriptable;
58 import org.htmlunit.javascript.JavaScriptEngine;
59 import org.htmlunit.javascript.configuration.JsxClass;
60 import org.htmlunit.javascript.configuration.JsxConstructor;
61 import org.htmlunit.javascript.configuration.JsxFunction;
62 import org.htmlunit.javascript.configuration.JsxGetter;
63 import org.htmlunit.javascript.configuration.JsxSetter;
64 import org.htmlunit.javascript.host.css.CSSStyleDeclaration;
65 import org.htmlunit.javascript.host.dom.Attr;
66 import org.htmlunit.javascript.host.dom.DOMException;
67 import org.htmlunit.javascript.host.dom.DOMTokenList;
68 import org.htmlunit.javascript.host.dom.Node;
69 import org.htmlunit.javascript.host.dom.NodeList;
70 import org.htmlunit.javascript.host.event.Event;
71 import org.htmlunit.javascript.host.event.EventHandler;
72 import org.htmlunit.javascript.host.event.UIEvent;
73 import org.htmlunit.javascript.host.html.HTMLCollection;
74 import org.htmlunit.javascript.host.html.HTMLElement;
75 import org.htmlunit.javascript.host.html.HTMLElement.ProxyDomNode;
76 import org.htmlunit.javascript.host.html.HTMLScriptElement;
77 import org.htmlunit.javascript.host.html.HTMLStyleElement;
78 import org.htmlunit.javascript.host.html.HTMLTemplateElement;
79 import org.htmlunit.util.StringUtils;
80 import org.xml.sax.SAXException;
81
82
83
84
85
86
87
88
89
90
91
92
93
94 @JsxClass(domClass = DomElement.class)
95 public class Element extends Node {
96
97 static final String POSITION_BEFORE_BEGIN = "beforebegin";
98 static final String POSITION_AFTER_BEGIN = "afterbegin";
99 static final String POSITION_BEFORE_END = "beforeend";
100 static final String POSITION_AFTER_END = "afterend";
101
102 private static final Pattern CLASS_NAMES_SPLIT_PATTERN = Pattern.compile("\\s");
103 private static final Pattern PRINT_NODE_PATTERN = Pattern.compile(" {2}");
104 private static final Pattern PRINT_NODE_QUOTE_PATTERN = Pattern.compile("\"");
105
106 private NamedNodeMap attributes_;
107 private Map<String, HTMLCollection> elementsByTagName_;
108 private int scrollLeft_;
109 private int scrollTop_;
110 private CSSStyleDeclaration style_;
111
112
113
114
115 @Override
116 @JsxConstructor
117 public void jsConstructor() {
118 super.jsConstructor();
119 }
120
121
122
123
124
125
126 @Override
127 public void setDomNode(final DomNode domNode) {
128 super.setDomNode(domNode);
129
130 final Window window = getWindow();
131 setParentScope(new WithScope(getTopLevelScope(getParentScope()), window.getDocument()));
132
133 style_ = new CSSStyleDeclaration(this, new ElementCssStyleDeclaration(getDomNodeOrDie()));
134
135
136
137 final DomElement htmlElt = (DomElement) domNode;
138 for (final DomAttr attr : htmlElt.getAttributesMap().values()) {
139 final String eventName = StringUtils.toRootLowerCase(attr.getName());
140 if (eventName.startsWith("on")) {
141 createEventHandler(eventName.substring(2), attr.getValue());
142 }
143 }
144 }
145
146
147
148
149
150
151
152 protected void createEventHandler(final String eventName, final String attrValue) {
153 final DomElement htmlElt = getDomNodeOrDie();
154
155
156 final BaseFunction eventHandler = new EventHandler(htmlElt, eventName, attrValue);
157 eventHandler.setPrototype(ScriptableObject.getClassPrototype(getParentScope(), "Function"));
158
159 setEventHandler(eventName, eventHandler);
160 }
161
162
163
164
165
166
167 @JsxGetter
168 public String getTagName() {
169 return getNodeName();
170 }
171
172
173
174
175
176
177
178 @Override
179 @JsxGetter
180 public NamedNodeMap getAttributes() {
181 if (attributes_ == null) {
182 attributes_ = createAttributesObject();
183 }
184 return attributes_;
185 }
186
187
188
189
190
191
192 protected NamedNodeMap createAttributesObject() {
193 return new NamedNodeMap(getDomNodeOrDie());
194 }
195
196
197
198
199
200
201
202 @JsxFunction
203 public String getAttribute(final String attributeName) {
204 String value = getDomNodeOrDie().getAttribute(attributeName);
205
206 if (ATTRIBUTE_NOT_DEFINED == value) {
207 value = null;
208 }
209
210 return value;
211 }
212
213
214
215
216
217
218
219 @JsxFunction
220 public void setAttribute(final String name, final String value) {
221 getDomNodeOrDie().setAttribute(name, value);
222 }
223
224
225
226
227
228
229
230 @JsxFunction
231 public HTMLCollection getElementsByTagName(final String tagName) {
232 if (elementsByTagName_ == null) {
233 elementsByTagName_ = new HashMap<>();
234 }
235
236 final String searchTagName;
237 final boolean caseSensitive;
238 final DomNode dom = getDomNodeOrNull();
239 if (dom == null) {
240 searchTagName = StringUtils.toRootLowerCase(tagName);
241 caseSensitive = false;
242 }
243 else {
244 final SgmlPage page = dom.getPage();
245 if (page != null && page.hasCaseSensitiveTagNames()) {
246 searchTagName = tagName;
247 caseSensitive = true;
248 }
249 else {
250 searchTagName = StringUtils.toRootLowerCase(tagName);
251 caseSensitive = false;
252 }
253 }
254
255 HTMLCollection collection = elementsByTagName_.get(searchTagName);
256 if (collection != null) {
257 return collection;
258 }
259
260 final DomNode node = getDomNodeOrDie();
261 collection = new HTMLCollection(node, false);
262 if (StringUtils.equalsChar('*', tagName)) {
263 collection.setIsMatchingPredicate((Predicate<DomNode> & Serializable) nodeToMatch -> true);
264 }
265 else {
266 collection.setIsMatchingPredicate(
267 (Predicate<DomNode> & Serializable) nodeToMatch -> {
268 if (caseSensitive) {
269 return searchTagName.equals(nodeToMatch.getNodeName());
270 }
271 return searchTagName.equalsIgnoreCase(nodeToMatch.getNodeName());
272 });
273 }
274
275 elementsByTagName_.put(tagName, collection);
276
277 return collection;
278 }
279
280
281
282
283
284
285
286 @JsxFunction
287 public HtmlUnitScriptable getAttributeNode(final String name) {
288 final Map<String, DomAttr> attributes = getDomNodeOrDie().getAttributesMap();
289 for (final DomAttr attr : attributes.values()) {
290 if (attr.getName().equals(name)) {
291 return attr.getScriptableObject();
292 }
293 }
294 return null;
295 }
296
297
298
299
300
301
302
303
304 @JsxFunction
305 public HTMLCollection getElementsByTagNameNS(final Object namespaceURI, final String localName) {
306 final HTMLCollection elements = new HTMLCollection(getDomNodeOrDie(), false);
307 elements.setIsMatchingPredicate(
308 (Predicate<DomNode> & Serializable)
309 node -> ("*".equals(namespaceURI) || Objects.equals(namespaceURI, node.getNamespaceURI()))
310 && ("*".equals(localName) || Objects.equals(localName, node.getLocalName())));
311 return elements;
312 }
313
314
315
316
317
318
319
320 @JsxFunction
321 public boolean hasAttribute(final String name) {
322 return getDomNodeOrDie().hasAttribute(name);
323 }
324
325
326
327
328 @Override
329 @JsxFunction
330 public boolean hasAttributes() {
331 return super.hasAttributes();
332 }
333
334
335
336
337 @Override
338 public DomElement getDomNodeOrDie() {
339 return (DomElement) super.getDomNodeOrDie();
340 }
341
342
343
344
345
346
347 @JsxFunction
348 public void removeAttribute(final String name) {
349 getDomNodeOrDie().removeAttribute(name);
350 }
351
352
353
354
355
356
357
358 @JsxFunction
359 public DOMRect getBoundingClientRect() {
360 final DOMRect textRectangle = new DOMRect(1, 1, 0, 0);
361 textRectangle.setParentScope(getTopLevelScope(getParentScope()));
362 textRectangle.setPrototype(getPrototype(textRectangle.getClass()));
363 return textRectangle;
364 }
365
366
367
368
369 @Override
370 @JsxGetter
371 public int getChildElementCount() {
372 return getDomNodeOrDie().getChildElementCount();
373 }
374
375
376
377
378 @Override
379 @JsxGetter
380 public Element getFirstElementChild() {
381 return super.getFirstElementChild();
382 }
383
384
385
386
387 @Override
388 @JsxGetter
389 public Element getLastElementChild() {
390 return super.getLastElementChild();
391 }
392
393
394
395
396
397
398 @JsxGetter
399 public Element getNextElementSibling() {
400 final DomElement child = getDomNodeOrDie().getNextElementSibling();
401 if (child != null) {
402 return child.getScriptableObject();
403 }
404 return null;
405 }
406
407
408
409
410
411
412 @JsxGetter
413 public Element getPreviousElementSibling() {
414 final DomElement child = getDomNodeOrDie().getPreviousElementSibling();
415 if (child != null) {
416 return child.getScriptableObject();
417 }
418 return null;
419 }
420
421
422
423
424
425
426
427 @Override
428 public Element getParentElement() {
429 Node parent = getParent();
430 while (parent != null && !(parent instanceof Element)) {
431 parent = parent.getParent();
432 }
433 return (Element) parent;
434 }
435
436
437
438
439 @Override
440 @JsxGetter
441 public HTMLCollection getChildren() {
442 return super.getChildren();
443 }
444
445
446
447
448
449
450 @JsxGetter
451 public DOMTokenList getClassList() {
452 return new DOMTokenList(this, "class");
453 }
454
455
456
457
458
459
460
461
462
463 @JsxFunction
464 public String getAttributeNS(final String namespaceURI, final String localName) {
465 final String value = getDomNodeOrDie().getAttributeNS(namespaceURI, localName);
466 if (ATTRIBUTE_NOT_DEFINED == value) {
467 return null;
468 }
469 return value;
470 }
471
472
473
474
475
476
477
478
479 @JsxFunction
480 public boolean hasAttributeNS(final String namespaceURI, final String localName) {
481 return getDomNodeOrDie().hasAttributeNS(namespaceURI, localName);
482 }
483
484
485
486
487
488
489
490
491 @JsxFunction
492 public void setAttributeNS(final String namespaceURI, final String qualifiedName, final String value) {
493 getDomNodeOrDie().setAttributeNS(namespaceURI, qualifiedName, value);
494 }
495
496
497
498
499
500
501
502 @JsxFunction
503 public void removeAttributeNS(final String namespaceURI, final String localName) {
504 getDomNodeOrDie().removeAttributeNS(namespaceURI, localName);
505 }
506
507
508
509
510
511
512
513 @JsxFunction
514 public Attr setAttributeNode(final Attr newAtt) {
515 final String name = newAtt.getName();
516
517 final NamedNodeMap nodes = getAttributes();
518 final Attr replacedAtt = (Attr) nodes.getNamedItemWithoutSytheticClassAttr(name);
519 if (replacedAtt != null) {
520 replacedAtt.detachFromParent();
521 }
522
523 final DomAttr newDomAttr = newAtt.getDomNodeOrDie();
524 getDomNodeOrDie().setAttributeNode(newDomAttr);
525 return replacedAtt;
526 }
527
528
529
530
531
532
533
534 @JsxFunction
535 public NodeList querySelectorAll(final String selectors) {
536 try {
537 return NodeList.staticNodeList(getParentScope(), getDomNodeOrDie().querySelectorAll(selectors));
538 }
539 catch (final CSSException e) {
540 throw JavaScriptEngine.asJavaScriptException(
541 getWindow(),
542 "An invalid or illegal selector was specified (selector: '"
543 + selectors + "' error: " + e.getMessage() + ").",
544 DOMException.SYNTAX_ERR);
545 }
546 }
547
548
549
550
551
552
553
554
555 @JsxFunction
556 public Node querySelector(final String selectors) {
557 try {
558 final DomNode node = getDomNodeOrDie().querySelector(selectors);
559 if (node != null) {
560 return node.getScriptableObject();
561 }
562 return null;
563 }
564 catch (final CSSException e) {
565 throw JavaScriptEngine.asJavaScriptException(
566 getWindow(),
567 "An invalid or illegal selector was specified (selector: '"
568 + selectors + "' error: " + e.getMessage() + ").",
569 DOMException.SYNTAX_ERR);
570 }
571 }
572
573
574
575
576
577
578 @JsxGetter(propertyName = "className")
579 public String getClassName_js() {
580 return getDomNodeOrDie().getAttributeDirect("class");
581 }
582
583
584
585
586
587
588 @JsxSetter(propertyName = "className")
589 public void setClassName_js(final String className) {
590 getDomNodeOrDie().setAttribute("class", className);
591 }
592
593
594
595
596
597
598 @JsxGetter
599 public int getClientHeight() {
600 final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
601 return style.getCalculatedHeight(false, true);
602 }
603
604
605
606
607
608
609 @JsxGetter
610 public int getClientWidth() {
611 final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
612 return style.getCalculatedWidth(false, true);
613 }
614
615
616
617
618
619
620 @JsxGetter
621 public int getClientLeft() {
622 final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
623 return style.getBorderLeftValue();
624 }
625
626
627
628
629
630
631 @JsxGetter
632 public int getClientTop() {
633 final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
634 return style.getBorderTopValue();
635 }
636
637
638
639
640
641
642
643
644 @JsxFunction
645 public HtmlUnitScriptable getAttributeNodeNS(final String namespaceURI, final String localName) {
646 return getDomNodeOrDie().getAttributeNodeNS(namespaceURI, localName).getScriptableObject();
647 }
648
649
650
651
652
653
654
655 @JsxFunction
656 public HTMLCollection getElementsByClassName(final String className) {
657 final DomElement elt = getDomNodeOrDie();
658 final String[] classNames = CLASS_NAMES_SPLIT_PATTERN.split(className, 0);
659
660 final HTMLCollection elements = new HTMLCollection(elt, true);
661
662 elements.setIsMatchingPredicate(
663 (Predicate<DomNode> & Serializable)
664 node -> {
665 if (!(node instanceof HtmlElement)) {
666 return false;
667 }
668 String classAttribute = ((HtmlElement) node).getAttributeDirect("class");
669 if (ATTRIBUTE_NOT_DEFINED == classAttribute) {
670 return false;
671 }
672
673 classAttribute = " " + classAttribute + " ";
674 for (final String aClassName : classNames) {
675 if (!classAttribute.contains(" " + aClassName + " ")) {
676 return false;
677 }
678 }
679 return true;
680 });
681
682 return elements;
683 }
684
685
686
687
688
689
690
691 @JsxFunction
692 public DOMRectList getClientRects() {
693 final TopLevel topScope = getTopLevelScope(getParentScope());
694 final DOMRectList rectList = new DOMRectList();
695 rectList.setParentScope(topScope);
696 rectList.setPrototype(getPrototype(rectList.getClass()));
697
698 if (!isDisplayNone() && getDomNodeOrDie().isAttachedToPage()) {
699 final DOMRect rect = new DOMRect(0, 0, 1, 1);
700 rect.setParentScope(topScope);
701 rect.setPrototype(getPrototype(rect.getClass()));
702 rectList.add(rect);
703 }
704
705 return rectList;
706 }
707
708
709
710
711
712
713
714 @JsxFunction
715 public Scriptable getAttributeNames() {
716 final org.w3c.dom.NamedNodeMap attributes = getDomNodeOrDie().getAttributes();
717
718 if (attributes.getLength() == 0) {
719 return JavaScriptEngine.newArray(getParentScope(), 0);
720 }
721
722 final ArrayList<String> res = new ArrayList<>();
723 for (int i = 0; i < attributes.getLength(); i++) {
724 res.add(attributes.item(i).getNodeName());
725 }
726
727 return JavaScriptEngine.newArray(getParentScope(), res.toArray());
728 }
729
730
731
732
733
734
735 protected final boolean isDisplayNone() {
736 Element element = this;
737 while (element != null) {
738 final CSSStyleDeclaration style = element.getWindow().getComputedStyle(element, null);
739 final String display = style.getDisplay();
740 if (DisplayStyle.NONE.value().equals(display)) {
741 return true;
742 }
743 element = element.getParentElement();
744 }
745 return false;
746 }
747
748
749
750
751
752
753
754
755
756
757 @JsxFunction
758 public Node insertAdjacentElement(final String where, final Object insertedElement) {
759 if (insertedElement instanceof Node insertedElementNode) {
760 final DomNode childNode = insertedElementNode.getDomNodeOrDie();
761 final Object[] values = getInsertAdjacentLocation(where);
762 final DomNode node = (DomNode) values[0];
763 final boolean append = ((Boolean) values[1]).booleanValue();
764
765 if (append) {
766 node.appendChild(childNode);
767 }
768 else {
769 node.insertBefore(childNode);
770 }
771 return insertedElementNode;
772 }
773 throw JavaScriptEngine.reportRuntimeError("Passed object is not an element: " + insertedElement);
774 }
775
776
777
778
779
780
781
782
783
784 @JsxFunction
785 public void insertAdjacentText(final String where, final String text) {
786 final Object[] values = getInsertAdjacentLocation(where);
787 final DomNode node = (DomNode) values[0];
788 final boolean append = ((Boolean) values[1]).booleanValue();
789
790 final DomText domText = new DomText(node.getPage(), text);
791
792 if (append) {
793 node.appendChild(domText);
794 }
795 else {
796 node.insertBefore(domText);
797 }
798 }
799
800
801
802
803
804
805
806
807
808
809
810 private Object[] getInsertAdjacentLocation(final String where) {
811 final DomNode currentNode = getDomNodeOrDie();
812 final DomNode node;
813 final boolean append;
814
815
816 if (POSITION_AFTER_BEGIN.equalsIgnoreCase(where)) {
817 if (currentNode.getFirstChild() == null) {
818
819 node = currentNode;
820 append = true;
821 }
822 else {
823
824 node = currentNode.getFirstChild();
825 append = false;
826 }
827 }
828 else if (POSITION_BEFORE_BEGIN.equalsIgnoreCase(where)) {
829
830 node = currentNode;
831 append = false;
832 }
833 else if (POSITION_BEFORE_END.equalsIgnoreCase(where)) {
834
835 node = currentNode;
836 append = true;
837 }
838 else if (POSITION_AFTER_END.equalsIgnoreCase(where)) {
839 if (currentNode.getNextSibling() == null) {
840
841 node = currentNode.getParentNode();
842 append = true;
843 }
844 else {
845
846 node = currentNode.getNextSibling();
847 append = false;
848 }
849 }
850 else {
851 throw JavaScriptEngine.reportRuntimeError("Illegal position value: \"" + where + "\"");
852 }
853
854 if (append) {
855 return new Object[] {node, Boolean.TRUE};
856 }
857 return new Object[] {node, Boolean.FALSE};
858 }
859
860
861
862
863
864
865
866
867
868 @JsxFunction
869 public void insertAdjacentHTML(final String position, final String text) {
870 final Object[] values = getInsertAdjacentLocation(position);
871 final DomNode domNode = (DomNode) values[0];
872 final boolean append = ((Boolean) values[1]).booleanValue();
873
874
875 final DomNode proxyDomNode = new ProxyDomNode(domNode.getPage(), domNode, append);
876 parseHtmlSnippet(proxyDomNode, text);
877 }
878
879
880
881
882
883
884
885
886
887
888 @JsxFunction({CHROME, EDGE, FF})
889 public static void moveBefore(final Context context, final VarScope scope,
890 final Scriptable thisObj, final Object[] args, final Function function) {
891 Node.moveBefore(context, scope, thisObj, args, function);
892 }
893
894
895
896
897
898
899
900 private static void parseHtmlSnippet(final DomNode target, final String source) {
901 try {
902 target.parseHtmlSnippet(source);
903 }
904 catch (final IOException | SAXException e) {
905 LogFactory.getLog(HtmlElement.class).error("Unexpected exception occurred while parsing HTML snippet", e);
906 throw JavaScriptEngine.reportRuntimeError("Unexpected exception occurred while parsing HTML snippet: "
907 + e.getMessage());
908 }
909 }
910
911
912
913
914
915
916 @JsxFunction
917 public String getHTML() {
918
919 return getInnerHTML();
920 }
921
922
923
924
925
926
927 @JsxGetter
928 public String getInnerHTML() {
929 try {
930 DomNode domNode = getDomNodeOrDie();
931 if (this instanceof HTMLTemplateElement) {
932 domNode = ((HtmlTemplate) getDomNodeOrDie()).getContent();
933 }
934 return getInnerHTML(domNode);
935 }
936 catch (final IllegalStateException e) {
937 throw JavaScriptEngine.typeError(e.getMessage());
938 }
939 }
940
941
942
943
944
945
946 @JsxSetter
947 public void setInnerHTML(final Object value) {
948 final DomElement domNode;
949 try {
950 domNode = getDomNodeOrDie();
951 }
952 catch (final IllegalStateException e) {
953 throw JavaScriptEngine.typeError(e.getMessage());
954 }
955
956 String html = null;
957 if (value != null) {
958 html = JavaScriptEngine.toString(value);
959 if (StringUtils.isEmptyString(html)) {
960 html = null;
961 }
962 }
963
964 try {
965 domNode.setInnerHtml(html);
966 }
967 catch (final IOException | SAXException e) {
968 LogFactory.getLog(HtmlElement.class).error("Unexpected exception occurred while parsing HTML snippet", e);
969 throw JavaScriptEngine.reportRuntimeError("Unexpected exception occurred while parsing HTML snippet: "
970 + e.getMessage());
971 }
972 }
973
974
975
976
977
978
979
980 protected String getInnerHTML(final DomNode domNode) {
981 final StringBuilder buf = new StringBuilder();
982
983 final String tagName = getTagName();
984 boolean isPlain = "SCRIPT".equals(tagName);
985
986 isPlain = isPlain || "STYLE".equals(tagName);
987
988
989 printChildren(buf, domNode, !isPlain);
990 return buf.toString();
991 }
992
993
994
995
996
997
998
999 @JsxGetter
1000 public String getOuterHTML() {
1001 final StringBuilder buf = new StringBuilder();
1002
1003 printNode(buf, getDomNodeOrDie(), true);
1004 return buf.toString();
1005 }
1006
1007
1008
1009
1010
1011
1012 @JsxSetter
1013 public void setOuterHTML(final Object value) {
1014 final DomNode domNode = getDomNodeOrDie();
1015 final DomNode parent = domNode.getParentNode();
1016 if (null == parent) {
1017 return;
1018 }
1019
1020 if (value == null) {
1021 domNode.remove();
1022 return;
1023 }
1024
1025 final String valueStr = JavaScriptEngine.toString(value);
1026 if (valueStr.isEmpty()) {
1027 domNode.remove();
1028 return;
1029 }
1030
1031 final DomNode nextSibling = domNode.getNextSibling();
1032 domNode.remove();
1033
1034 final DomNode target;
1035 final boolean append;
1036 if (nextSibling != null) {
1037 target = nextSibling;
1038 append = false;
1039 }
1040 else {
1041 target = parent;
1042 append = true;
1043 }
1044
1045 final DomNode proxyDomNode = new ProxyDomNode(target.getPage(), target, append);
1046 parseHtmlSnippet(proxyDomNode, valueStr);
1047 }
1048
1049
1050
1051
1052
1053
1054
1055
1056 protected final void printChildren(final StringBuilder builder, final DomNode node, final boolean html) {
1057 if (node instanceof HtmlTemplate template) {
1058
1059 for (final DomNode child : template.getContent().getChildren()) {
1060 printNode(builder, child, html);
1061 }
1062 return;
1063 }
1064
1065 for (final DomNode child : node.getChildren()) {
1066 printNode(builder, child, html);
1067 }
1068 }
1069
1070 protected void printNode(final StringBuilder builder, final DomNode node, final boolean html) {
1071 if (node instanceof DomComment) {
1072 if (html) {
1073
1074 final String s = PRINT_NODE_PATTERN.matcher(node.getNodeValue()).replaceAll(" ");
1075 builder.append("<!--").append(s).append("-->");
1076 }
1077 }
1078 else if (node instanceof DomCDataSection) {
1079 builder.append("<![CDATA[").append(node.getNodeValue()).append("]]>");
1080 }
1081 else if (node instanceof DomCharacterData) {
1082
1083 String s = node.getNodeValue();
1084 if (html) {
1085 s = StringUtils.escapeXmlChars(s);
1086 }
1087 builder.append(s);
1088 }
1089 else if (html) {
1090 final DomElement element = (DomElement) node;
1091 final Element scriptObject = node.getScriptableObject();
1092 final String tag = element.getTagName();
1093
1094 Element htmlElement = null;
1095 if (scriptObject instanceof HTMLElement) {
1096 htmlElement = scriptObject;
1097 }
1098 builder.append('<').append(tag);
1099 for (final DomAttr attr : element.getAttributesMap().values()) {
1100 if (!attr.getSpecified()) {
1101 continue;
1102 }
1103
1104 final String name = attr.getName();
1105 final String value = PRINT_NODE_QUOTE_PATTERN.matcher(attr.getValue()).replaceAll(""");
1106 builder.append(' ').append(name).append("=\"").append(value).append('\"');
1107 }
1108 builder.append('>');
1109
1110 final boolean isHtml = !(scriptObject instanceof HTMLScriptElement)
1111 && !(scriptObject instanceof HTMLStyleElement);
1112 printChildren(builder, node, isHtml);
1113 if (null == htmlElement || !htmlElement.isEndTagForbidden()) {
1114 builder.append("</").append(tag).append('>');
1115 }
1116 }
1117 else {
1118 if (node instanceof HtmlElement element) {
1119 if (StringUtils.equalsChar('p', element.getTagName())) {
1120 int i = builder.length() - 1;
1121 while (i >= 0 && Character.isWhitespace(builder.charAt(i))) {
1122 i--;
1123 }
1124 builder.setLength(i + 1);
1125 builder.append('\n');
1126 }
1127 if (!"script".equals(element.getTagName())) {
1128 printChildren(builder, node, html);
1129 }
1130 }
1131 }
1132 }
1133
1134
1135
1136
1137
1138
1139
1140 protected boolean isEndTagForbidden() {
1141 return false;
1142 }
1143
1144
1145
1146
1147
1148
1149 @JsxGetter
1150 public String getId() {
1151 return getDomNodeOrDie().getId();
1152 }
1153
1154
1155
1156
1157
1158
1159 @JsxSetter
1160 public void setId(final String newId) {
1161 getDomNodeOrDie().setId(newId);
1162 }
1163
1164
1165
1166
1167
1168
1169 @JsxFunction
1170 public void removeAttributeNode(final Attr attribute) {
1171 final String name = attribute.getName();
1172 final String namespaceUri = attribute.getNamespaceURI();
1173 removeAttributeNS(namespaceUri, name);
1174 }
1175
1176
1177
1178
1179
1180
1181
1182 @JsxGetter
1183 public int getScrollTop() {
1184
1185
1186 if (scrollTop_ < 0) {
1187 scrollTop_ = 0;
1188 }
1189 else if (scrollTop_ > 0) {
1190 final ComputedCssStyleDeclaration style =
1191 getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
1192 if (!style.isScrollable(false)) {
1193 scrollTop_ = 0;
1194 }
1195 }
1196 return scrollTop_;
1197 }
1198
1199
1200
1201
1202
1203
1204 @JsxSetter
1205 public void setScrollTop(final int scroll) {
1206 scrollTop_ = scroll;
1207 }
1208
1209
1210
1211
1212
1213
1214
1215 @JsxGetter
1216 public int getScrollLeft() {
1217
1218
1219 if (scrollLeft_ < 0) {
1220 scrollLeft_ = 0;
1221 }
1222 else if (scrollLeft_ > 0) {
1223 final ComputedCssStyleDeclaration style =
1224 getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
1225 if (!style.isScrollable(true)) {
1226 scrollLeft_ = 0;
1227 }
1228 }
1229 return scrollLeft_;
1230 }
1231
1232
1233
1234
1235
1236
1237 @JsxSetter
1238 public void setScrollLeft(final int scroll) {
1239 scrollLeft_ = scroll;
1240 }
1241
1242
1243
1244
1245
1246
1247
1248
1249 @JsxGetter
1250 public int getScrollHeight() {
1251 return getClientHeight();
1252 }
1253
1254
1255
1256
1257
1258
1259
1260
1261 @JsxGetter
1262 public int getScrollWidth() {
1263 return getClientWidth();
1264 }
1265
1266
1267
1268
1269
1270
1271 protected CSSStyleDeclaration getStyle() {
1272 return style_;
1273 }
1274
1275
1276
1277
1278
1279
1280 protected void setStyle(final String style) {
1281 getStyle().setCssText(style);
1282 }
1283
1284
1285
1286
1287
1288
1289
1290 @JsxFunction
1291 public void scroll(final Scriptable x, final Scriptable y) {
1292 scrollTo(x, y);
1293 }
1294
1295
1296
1297
1298
1299
1300
1301 @JsxFunction
1302 public void scrollBy(final Scriptable x, final Scriptable y) {
1303 int xOff = 0;
1304 int yOff = 0;
1305 if (y != null) {
1306 xOff = JavaScriptEngine.toInt32(x);
1307 yOff = JavaScriptEngine.toInt32(y);
1308 }
1309 else {
1310 if (!(x instanceof NativeObject)) {
1311 throw JavaScriptEngine.typeError("eee");
1312 }
1313 if (x.has("left", x)) {
1314 xOff = JavaScriptEngine.toInt32(x.get("left", x));
1315 }
1316 if (x.has("top", x)) {
1317 yOff = JavaScriptEngine.toInt32(x.get("top", x));
1318 }
1319 }
1320
1321 setScrollLeft(getScrollLeft() + xOff);
1322 setScrollTop(getScrollTop() + yOff);
1323
1324 fireScrollEvent(this);
1325 }
1326
1327 private void fireScrollEvent(final Node node) {
1328 final Event event;
1329 if (getBrowserVersion().hasFeature(EVENT_SCROLL_UIEVENT)) {
1330 event = new UIEvent(node, Event.TYPE_SCROLL);
1331 }
1332 else {
1333 event = new Event(node, Event.TYPE_SCROLL);
1334 event.setCancelable(false);
1335 }
1336 event.setBubbles(false);
1337 node.fireEvent(event);
1338 }
1339
1340 private void fireScrollEvent(final Window window) {
1341 final Event event;
1342 if (getBrowserVersion().hasFeature(EVENT_SCROLL_UIEVENT)) {
1343 event = new UIEvent(window.getDocument(), Event.TYPE_SCROLL);
1344 }
1345 else {
1346 event = new Event(window.getDocument(), Event.TYPE_SCROLL);
1347 event.setCancelable(false);
1348 }
1349 window.fireEvent(event);
1350 }
1351
1352
1353
1354
1355
1356
1357
1358 @JsxFunction
1359 public void scrollTo(final Scriptable x, final Scriptable y) {
1360 int xOff;
1361 int yOff;
1362 if (y != null) {
1363 xOff = JavaScriptEngine.toInt32(x);
1364 yOff = JavaScriptEngine.toInt32(y);
1365 }
1366 else {
1367 if (!(x instanceof NativeObject)) {
1368 throw JavaScriptEngine.typeError("eee");
1369 }
1370
1371 xOff = getScrollLeft();
1372 yOff = getScrollTop();
1373 if (x.has("left", x)) {
1374 xOff = JavaScriptEngine.toInt32(x.get("left", x));
1375 }
1376 if (x.has("top", x)) {
1377 yOff = JavaScriptEngine.toInt32(x.get("top", x));
1378 }
1379 }
1380
1381 setScrollLeft(xOff);
1382 setScrollTop(yOff);
1383
1384 fireScrollEvent(this);
1385 }
1386
1387
1388
1389
1390
1391
1392 @JsxFunction
1393 public void scrollIntoView() {
1394
1395
1396
1397
1398 Node parent = getParent();
1399 while (parent != null) {
1400 if (parent instanceof HTMLElement) {
1401 fireScrollEvent(parent);
1402 }
1403
1404 parent = parent.getParent();
1405 }
1406 fireScrollEvent(getWindow());
1407 }
1408
1409
1410
1411
1412
1413 @JsxFunction({CHROME, EDGE})
1414 public void scrollIntoViewIfNeeded() {
1415
1416 }
1417
1418
1419
1420
1421 @Override
1422 @JsxGetter
1423 public String getPrefix() {
1424 return super.getPrefix();
1425 }
1426
1427
1428
1429
1430 @Override
1431 @JsxGetter
1432 public String getLocalName() {
1433 return super.getLocalName();
1434 }
1435
1436
1437
1438
1439 @Override
1440 @JsxGetter
1441 public String getNamespaceURI() {
1442 return super.getNamespaceURI();
1443 }
1444
1445
1446
1447
1448
1449
1450 @JsxGetter({CHROME, EDGE})
1451 public Function getOnbeforecopy() {
1452 return getEventHandler(Event.TYPE_BEFORECOPY);
1453 }
1454
1455
1456
1457
1458
1459
1460 @JsxSetter({CHROME, EDGE})
1461 public void setOnbeforecopy(final Object onbeforecopy) {
1462 setEventHandler(Event.TYPE_BEFORECOPY, onbeforecopy);
1463 }
1464
1465
1466
1467
1468
1469
1470 @JsxGetter({CHROME, EDGE})
1471 public Function getOnbeforecut() {
1472 return getEventHandler(Event.TYPE_BEFORECUT);
1473 }
1474
1475
1476
1477
1478
1479
1480 @JsxSetter({CHROME, EDGE})
1481 public void setOnbeforecut(final Object onbeforecut) {
1482 setEventHandler(Event.TYPE_BEFORECUT, onbeforecut);
1483 }
1484
1485
1486
1487
1488
1489
1490 @JsxGetter({CHROME, EDGE})
1491 public Function getOnbeforepaste() {
1492 return getEventHandler(Event.TYPE_BEFOREPASTE);
1493 }
1494
1495
1496
1497
1498
1499
1500 @JsxSetter({CHROME, EDGE})
1501 public void setOnbeforepaste(final Object onbeforepaste) {
1502 setEventHandler(Event.TYPE_BEFOREPASTE, onbeforepaste);
1503 }
1504
1505
1506
1507
1508
1509
1510 @JsxGetter({CHROME, EDGE})
1511 public Function getOnsearch() {
1512 return getEventHandler(Event.TYPE_SEARCH);
1513 }
1514
1515
1516
1517
1518
1519
1520 @JsxSetter({CHROME, EDGE})
1521 public void setOnsearch(final Object onsearch) {
1522 setEventHandler(Event.TYPE_SEARCH, onsearch);
1523 }
1524
1525
1526
1527
1528
1529
1530 @JsxGetter({CHROME, EDGE})
1531 public Function getOnwebkitfullscreenchange() {
1532 return getEventHandler(Event.TYPE_WEBKITFULLSCREENCHANGE);
1533 }
1534
1535
1536
1537
1538
1539
1540 @JsxSetter({CHROME, EDGE})
1541 public void setOnwebkitfullscreenchange(final Object onwebkitfullscreenchange) {
1542 setEventHandler(Event.TYPE_WEBKITFULLSCREENCHANGE, onwebkitfullscreenchange);
1543 }
1544
1545
1546
1547
1548
1549
1550 @JsxGetter({CHROME, EDGE})
1551 public Function getOnwebkitfullscreenerror() {
1552 return getEventHandler(Event.TYPE_WEBKITFULLSCREENERROR);
1553 }
1554
1555
1556
1557
1558
1559
1560 @JsxSetter({CHROME, EDGE})
1561 public void setOnwebkitfullscreenerror(final Object onwebkitfullscreenerror) {
1562 setEventHandler(Event.TYPE_WEBKITFULLSCREENERROR, onwebkitfullscreenerror);
1563 }
1564
1565
1566
1567
1568
1569
1570 public Function getOnwheel() {
1571 return getEventHandler(Event.TYPE_WHEEL);
1572 }
1573
1574
1575
1576
1577
1578
1579 public void setOnwheel(final Object onwheel) {
1580 setEventHandler(Event.TYPE_WHEEL, onwheel);
1581 }
1582
1583
1584
1585
1586 @Override
1587 @JsxFunction
1588 public void remove() {
1589 super.remove();
1590 }
1591
1592
1593
1594
1595
1596
1597
1598
1599 @JsxFunction({FF, FF_ESR})
1600 public void setCapture(final boolean retargetToElement) {
1601
1602 }
1603
1604
1605
1606
1607
1608 @JsxFunction({FF, FF_ESR})
1609 public void releaseCapture() {
1610
1611 }
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623 @JsxFunction
1624 public static void before(final Context context, final VarScope scope,
1625 final Scriptable thisObj, final Object[] args, final Function function) {
1626 Node.before(context, thisObj, args, function);
1627 }
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639 @JsxFunction
1640 public static void after(final Context context, final VarScope scope,
1641 final Scriptable thisObj, final Object[] args, final Function function) {
1642 Node.after(context, thisObj, args, function);
1643 }
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654 @JsxFunction
1655 public static void replaceWith(final Context context, final VarScope scope,
1656 final Scriptable thisObj, final Object[] args, final Function function) {
1657 Node.replaceWith(context, thisObj, args, function);
1658 }
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670 @JsxFunction
1671 public static boolean matches(final Context context, final VarScope scope,
1672 final Scriptable thisObj, final Object[] args, final Function function) {
1673 if (!(thisObj instanceof Element)) {
1674 throw JavaScriptEngine.typeError("Illegal invocation");
1675 }
1676
1677 final String selectorString = (String) args[0];
1678 try {
1679 final DomNode domNode = ((Element) thisObj).getDomNodeOrNull();
1680 return domNode != null && ((DomElement) domNode).matches(selectorString);
1681 }
1682 catch (final CSSException e) {
1683 throw JavaScriptEngine.asJavaScriptException(
1684 (HtmlUnitScriptable) getTopLevelScope(scope).getGlobalThis(),
1685 "An invalid or illegal selector was specified (selector: '"
1686 + selectorString + "' error: " + e.getMessage() + ").",
1687 DOMException.SYNTAX_ERR);
1688 }
1689 }
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702 @JsxFunction({FF, FF_ESR})
1703 public static boolean mozMatchesSelector(final Context context, final VarScope scope,
1704 final Scriptable thisObj, final Object[] args, final Function function) {
1705 return matches(context, scope, thisObj, args, function);
1706 }
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719 @JsxFunction
1720 public static boolean webkitMatchesSelector(final Context context, final VarScope scope,
1721 final Scriptable thisObj, final Object[] args, final Function function) {
1722 return matches(context, scope, thisObj, args, function);
1723 }
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735 @JsxFunction
1736 public static Element closest(final Context context, final VarScope scope,
1737 final Scriptable thisObj, final Object[] args, final Function function) {
1738 if (!(thisObj instanceof Element)) {
1739 throw JavaScriptEngine.typeError("Illegal invocation");
1740 }
1741
1742 final String selectorString = (String) args[0];
1743 try {
1744 final DomNode domNode = ((Element) thisObj).getDomNodeOrNull();
1745 if (domNode == null) {
1746 return null;
1747 }
1748 final DomElement elem = domNode.closest(selectorString);
1749 if (elem == null) {
1750 return null;
1751 }
1752 return elem.getScriptableObject();
1753 }
1754 catch (final CSSException e) {
1755 throw JavaScriptEngine.syntaxError(
1756 "An invalid or illegal selector was specified (selector: '"
1757 + selectorString + "' error: " + e.getMessage() + ").");
1758 }
1759 }
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771 @JsxFunction
1772 public boolean toggleAttribute(final String name, final Object force) {
1773 if (JavaScriptEngine.isUndefined(force)) {
1774 if (hasAttribute(name)) {
1775 removeAttribute(name);
1776 return false;
1777 }
1778 setAttribute(name, "");
1779 return true;
1780 }
1781 if (JavaScriptEngine.toBoolean(force)) {
1782 setAttribute(name, "");
1783 return true;
1784 }
1785 removeAttribute(name);
1786 return false;
1787 }
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799 @JsxFunction
1800 public static void append(final Context context, final VarScope scope,
1801 final Scriptable thisObj, final Object[] args, final Function function) {
1802 if (!(thisObj instanceof Element)) {
1803 throw JavaScriptEngine.typeError("Illegal invocation");
1804 }
1805
1806 Node.append(context, thisObj, args, function);
1807 }
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819 @JsxFunction
1820 public static void prepend(final Context context, final VarScope scope,
1821 final Scriptable thisObj, final Object[] args, final Function function) {
1822 if (!(thisObj instanceof Element)) {
1823 throw JavaScriptEngine.typeError("Illegal invocation");
1824 }
1825
1826 Node.prepend(context, thisObj, args, function);
1827 }
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839 @JsxFunction
1840 public static void replaceChildren(final Context context, final VarScope scope,
1841 final Scriptable thisObj, final Object[] args, final Function function) {
1842 if (!(thisObj instanceof Element)) {
1843 throw JavaScriptEngine.typeError("Illegal invocation");
1844 }
1845
1846 Node.replaceChildren(context, thisObj, args, function);
1847 }
1848 }