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