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
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 }