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