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