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