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