View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
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   * A JavaScript object for {@code Element}.
81   *
82   * @author Ahmed Ashour
83   * @author Marc Guillemot
84   * @author Sudhan Moghe
85   * @author Ronald Brill
86   * @author Frank Danek
87   * @author Anton Demydenko
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_; // for performance and for equality (==)
103     private int scrollLeft_;
104     private int scrollTop_;
105     private CSSStyleDeclaration style_;
106 
107     /**
108      * JavaScript constructor.
109      */
110     @Override
111     @JsxConstructor
112     public void jsConstructor() {
113         super.jsConstructor();
114     }
115 
116     /**
117      * Sets the DOM node that corresponds to this JavaScript object.
118      * @param domNode the DOM node
119      */
120     @Override
121     public void setDomNode(final DomNode domNode) {
122         super.setDomNode(domNode);
123 
124         setParentScope(getWindow().getDocument());
125         // CSSStyleDeclaration uses the parent scope
126         style_ = new CSSStyleDeclaration(this, new ElementCssStyleDeclaration(getDomNodeOrDie()));
127 
128         // Convert JavaScript snippets defined in the attribute map to executable event handlers.
129         //Should be called only on construction.
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      * Create the event handler function from the attribute value.
141      * @param eventName the event name (ex: "onclick")
142      * @param attrValue the attribute value
143      */
144     protected void createEventHandler(final String eventName, final String attrValue) {
145         final DomElement htmlElt = getDomNodeOrDie();
146 
147         // TODO: check that it is an "allowed" event for the browser, and take care to the case
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      * Returns the tag name of this element.
156      * @return the tag name
157      */
158     @JsxGetter
159     public String getTagName() {
160         return getNodeName();
161     }
162 
163     /**
164      * Returns the attributes of this XML element.
165      * @see <a href="https://developer.mozilla.org/en-US/docs/DOM/Node.attributes">Gecko DOM Reference</a>
166      * @return the attributes of this XML element
167      */
168     @Override
169     @JsxGetter
170     public NamedNodeMap getAttributes() {
171         if (attributes_ == null) {
172             attributes_ = createAttributesObject();
173         }
174         return attributes_;
175     }
176 
177     /**
178      * Creates the JS object for the property attributes. This object will the be cached.
179      * @return the JS object
180      */
181     protected NamedNodeMap createAttributesObject() {
182         return new NamedNodeMap(getDomNodeOrDie());
183     }
184 
185     /**
186      * @param attributeName attribute name
187      * @return the value of the specified attribute, {@code null} if the attribute is not defined
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      * Sets an attribute.
202      *
203      * @param name Name of the attribute to set
204      * @param value Value to set the attribute to
205      */
206     @JsxFunction
207     public void setAttribute(final String name, final String value) {
208         getDomNodeOrDie().setAttribute(name, value);
209     }
210 
211     /**
212      * Returns all the descendant elements with the specified tag name.
213      * @param tagName the name to search for
214      * @return all the descendant elements with the specified tag name
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      * Retrieves an attribute node by name.
268      * @param name the name of the attribute to retrieve
269      * @return the XMLAttr node with the specified name or {@code null} if there is no such attribute
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      * Returns a list of elements with the given tag name belonging to the given namespace.
284      * @param namespaceURI the namespace URI of elements to look for
285      * @param localName is either the local name of elements to look for or the special value "*",
286      *                  which matches all elements.
287      * @return a live NodeList of found elements in the order they appear in the tree
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      * Returns true when an attribute with a given name is specified on this element or has a default value.
301      * See also <a href="http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-ElHasAttr">
302      * the DOM reference</a>
303      * @param name the name of the attribute to look for
304      * @return true if an attribute with the given name is specified on this element or has a default value
305      */
306     @JsxFunction
307     public boolean hasAttribute(final String name) {
308         return getDomNodeOrDie().hasAttribute(name);
309     }
310 
311     /**
312      * {@inheritDoc}
313      */
314     @Override
315     @JsxFunction
316     public boolean hasAttributes() {
317         return super.hasAttributes();
318     }
319 
320     /**
321      * {@inheritDoc}
322      */
323     @Override
324     public DomElement getDomNodeOrDie() {
325         return (DomElement) super.getDomNodeOrDie();
326     }
327 
328     /**
329      * Removes the specified attribute.
330      * @param name the name of the attribute to remove
331      */
332     @JsxFunction
333     public void removeAttribute(final String name) {
334         getDomNodeOrDie().removeAttribute(name);
335     }
336 
337     /**
338      * Retrieves an object that specifies the bounds of a collection of TextRectangle objects.
339      * @see <a href="http://msdn.microsoft.com/en-us/library/ms536433.aspx">MSDN doc</a>
340      * @return an object that specifies the bounds of a collection of TextRectangle objects
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      * {@inheritDoc}
352      */
353     @Override
354     @JsxGetter
355     public int getChildElementCount() {
356         return getDomNodeOrDie().getChildElementCount();
357     }
358 
359     /**
360      * {@inheritDoc}
361      */
362     @Override
363     @JsxGetter
364     public Element getFirstElementChild() {
365         return super.getFirstElementChild();
366     }
367 
368     /**
369      * {@inheritDoc}
370      */
371     @Override
372     @JsxGetter
373     public Element getLastElementChild() {
374         return super.getLastElementChild();
375     }
376 
377     /**
378      * Returns the next element sibling.
379      * @return the next element sibling
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      * Returns the previous element sibling.
392      * @return the previous element sibling
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      * Gets the first ancestor instance of {@link Element}. It is mostly identical
405      * to {@link #getParent()} except that it skips non {@link Element} nodes.
406      * @return the parent element
407      * @see #getParent()
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      * {@inheritDoc}
420      */
421     @Override
422     @JsxGetter
423     public HTMLCollection getChildren() {
424         return super.getChildren();
425     }
426 
427     /**
428      * Gets the token list of class attribute.
429      * @return the token list of class attribute
430      */
431     @JsxGetter
432     public DOMTokenList getClassList() {
433         return new DOMTokenList(this, "class");
434     }
435 
436     /**
437      * Gets the specified attribute.
438      * @param namespaceURI the namespace URI
439      * @param localName the local name of the attribute to look for
440      * @return the value of the specified attribute, {@code null} if the attribute is not defined
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      * Test for attribute.
453      * See also <a href="http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-ElHasAttrNS">
454      * the DOM reference</a>
455      *
456      * @param namespaceURI the namespace URI
457      * @param localName the local name of the attribute to look for
458      * @return {@code true} if the node has this attribute
459      */
460     @JsxFunction
461     public boolean hasAttributeNS(final String namespaceURI, final String localName) {
462         return getDomNodeOrDie().hasAttributeNS(namespaceURI, localName);
463     }
464 
465     /**
466      * Sets the specified attribute.
467      * @param namespaceURI the namespace URI
468      * @param qualifiedName the qualified name of the attribute to look for
469      * @param value the new attribute value
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      * Removes the specified attribute.
478      * @param namespaceURI the namespace URI of the attribute to remove
479      * @param localName the local name of the attribute to remove
480      */
481     @JsxFunction
482     public void removeAttributeNS(final String namespaceURI, final String localName) {
483         getDomNodeOrDie().removeAttributeNS(namespaceURI, localName);
484     }
485 
486     /**
487      * Sets the attribute node for the specified attribute.
488      * @param newAtt the attribute to set
489      * @return the replaced attribute node, if any
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      * Retrieves all element nodes from descendants of the starting element node that match any selector
508      * within the supplied selector strings.
509      * The NodeList object returned by the querySelectorAll() method must be static, not live.
510      * @param selectors the selectors
511      * @return the static node list
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      * Returns the first element within the document that matches the specified group of selectors.
529      * @param selectors the selectors
530      * @return null if no matches are found; otherwise, it returns the first matching element
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      * Returns the class defined for this element.
552      * @return the class name
553      */
554     @JsxGetter(propertyName = "className")
555     public String getClassName_js() {
556         return getDomNodeOrDie().getAttributeDirect("class");
557     }
558 
559     /**
560      * Sets the class attribute for this element.
561      * @param className the new class name
562      */
563     @JsxSetter(propertyName = "className")
564     public void setClassName_js(final String className) {
565         getDomNodeOrDie().setAttribute("class", className);
566     }
567 
568     /**
569      * Returns the {@code clientHeight} attribute.
570      * @return the {@code clientHeight} attribute
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      * Returns the {@code clientWidth} attribute.
580      * @return the {@code clientWidth} attribute
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      * Returns the {@code clientLeft} attribute.
590      * @return the {@code clientLeft} attribute
591      */
592     @JsxGetter
593     public int getClientLeft() {
594         final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
595         return style.getBorderLeftValue();
596     }
597 
598     /**
599      * Returns {@code clientTop} attribute.
600      * @return the {@code clientTop} attribute
601      */
602     @JsxGetter
603     public int getClientTop() {
604         final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
605         return style.getBorderTopValue();
606     }
607 
608     /**
609      * Returns the specified attribute.
610      * @param namespaceURI the namespace URI
611      * @param localName the local name of the attribute to look for
612      * @return the specified attribute, {@code null} if the attribute is not defined
613      */
614     @JsxFunction
615     public HtmlUnitScriptable getAttributeNodeNS(final String namespaceURI, final String localName) {
616         return getDomNodeOrDie().getAttributeNodeNS(namespaceURI, localName).getScriptableObject();
617     }
618 
619     /**
620      * Returns all the descendant elements with the specified class.
621      * @param className the name to search for
622      * @return all the descendant elements with the specified class name
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; // probably better performance as most of elements won't have a class attribute
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      * Retrieves a collection of rectangles that describes the layout of the contents of an object
656      * or range within the client. Each rectangle describes a single line.
657      * @return a collection of rectangles that describes the layout of the contents
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      * Returns whether the {@code display} is {@code none} or not.
678      * @return whether the {@code display} is {@code none} or not
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      * Inserts the given element into the element at the location.
695      * @param where specifies where to insert the element, using one of the following values (case-insensitive):
696      *        beforebegin, afterbegin, beforeend, afterend
697      * @param insertedElement the element to be inserted
698      * @return an element object
699      *
700      * @see <a href="http://msdn.microsoft.com/en-us/library/ie/ms536451.aspx">MSDN</a>
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      * Inserts the given text into the element at the specified location.
724      * @param where specifies where to insert the text, using one of the following values (case-insensitive):
725      *      beforebegin, afterbegin, beforeend, afterend
726      * @param text the text to insert
727      *
728      * @see <a href="http://msdn.microsoft.com/en-us/library/ie/ms536453.aspx">MSDN</a>
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         // add the new nodes
738         if (append) {
739             node.appendChild(domText);
740         }
741         else {
742             node.insertBefore(domText);
743         }
744     }
745 
746     /**
747      * Returns where and how to add the new node.
748      * Used by {@link #insertAdjacentHTML(String, String)},
749      * {@link #insertAdjacentElement(String, Object)} and
750      * {@link #insertAdjacentText(String, String)}.
751      * @param where specifies where to insert the element, using one of the following values (case-insensitive):
752      *        beforebegin, afterbegin, beforeend, afterend
753      * @return an array of 1-DomNode:parentNode and 2-Boolean:append
754      */
755     private Object[] getInsertAdjacentLocation(final String where) {
756         final DomNode currentNode = getDomNodeOrDie();
757         final DomNode node;
758         final boolean append;
759 
760         // compute the where and how the new nodes should be added
761         if (POSITION_AFTER_BEGIN.equalsIgnoreCase(where)) {
762             if (currentNode.getFirstChild() == null) {
763                 // new nodes should appended to the children of current node
764                 node = currentNode;
765                 append = true;
766             }
767             else {
768                 // new nodes should be inserted before first child
769                 node = currentNode.getFirstChild();
770                 append = false;
771             }
772         }
773         else if (POSITION_BEFORE_BEGIN.equalsIgnoreCase(where)) {
774             // new nodes should be inserted before current node
775             node = currentNode;
776             append = false;
777         }
778         else if (POSITION_BEFORE_END.equalsIgnoreCase(where)) {
779             // new nodes should appended to the children of current node
780             node = currentNode;
781             append = true;
782         }
783         else if (POSITION_AFTER_END.equalsIgnoreCase(where)) {
784             if (currentNode.getNextSibling() == null) {
785                 // new nodes should appended to the children of parent node
786                 node = currentNode.getParentNode();
787                 append = true;
788             }
789             else {
790                 // new nodes should be inserted before current node's next sibling
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      * Parses the given text as HTML or XML and inserts the resulting nodes into the tree in the position given by the
807      * position argument.
808      * @param position specifies where to insert the nodes, using one of the following values (case-insensitive):
809      *        <code>beforebegin</code>, <code>afterbegin</code>, <code>beforeend</code>, <code>afterend</code>
810      * @param text the text to parse
811      *
812      * @see <a href="http://www.w3.org/TR/DOM-Parsing/#methods-2">W3C Spec</a>
813      * @see <a href="http://domparsing.spec.whatwg.org/#dom-element-insertadjacenthtml">WhatWG Spec</a>
814      * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element.insertAdjacentHTML"
815      *      >Mozilla Developer Network</a>
816      * @see <a href="http://msdn.microsoft.com/en-us/library/ie/ms536452.aspx">MSDN</a>
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         // add the new nodes
825         final DomNode proxyDomNode = new ProxyDomNode(domNode.getPage(), domNode, append);
826         parseHtmlSnippet(proxyDomNode, text);
827     }
828 
829     /**
830      * Parses the specified HTML source code, appending the resulting content at the specified target location.
831      * @param target the node indicating the position at which the parsed content should be placed
832      * @param source the HTML code extract to parse
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      * The {@code getHTML} function.
847      * @return the contents of this node as HTML
848      */
849     @JsxFunction({CHROME, EDGE, FF})
850     public String getHTML() {
851         // ignore the params because we have no shadow dom support so far
852         return getInnerHTML();
853     }
854 
855     /**
856      * Gets the {@code innerHTML} attribute.
857      * @return the contents of this node as HTML
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      * Replaces all child elements of this element with the supplied value.
875      * @param value the new value for the contents of this element
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      * Helper for getInnerHtml (to be reuses bei HTMLTemplate.
907      * @param domNode the node
908      * @return the contents of this node as HTML
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         // we can't rely on DomNode.asXml because it adds indentation and new lines
919         printChildren(buf, domNode, !isPlain);
920         return buf.toString();
921     }
922 
923     /**
924      * Gets the outerHTML of the node.
925      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534310.aspx">MSDN documentation</a>
926      * @return the contents of this node as HTML
927      */
928     @JsxGetter
929     public String getOuterHTML() {
930         final StringBuilder buf = new StringBuilder();
931         // we can't rely on DomNode.asXml because it adds indentation and new lines
932         printNode(buf, getDomNodeOrDie(), true);
933         return buf.toString();
934     }
935 
936     /**
937      * Replaces this element (including all child elements) with the supplied value.
938      * @param value the new value for replacing this element
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      * Helper for getting code back from nodes.
985      * @param builder the builder to write to
986      * @param node the node to be serialized
987      * @param html flag
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                 // Remove whitespace sequences.
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             // Remove whitespace sequences, possibly escape XML characters.
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("&quot;");
1040                 builder.append(' ').append(name).append("=\"").append(value).append('\"');
1041             }
1042             builder.append('>');
1043             // Add the children.
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      * Returns whether the end tag is forbidden or not.
1071      * @see <a href="http://www.w3.org/TR/html4/index/elements.html">HTML 4 specs</a>
1072      * @return whether the end tag is forbidden or not
1073      */
1074     protected boolean isEndTagForbidden() {
1075         return false;
1076     }
1077 
1078     /**
1079      * Returns the element ID.
1080      * @return the ID of this element
1081      */
1082     @JsxGetter
1083     public String getId() {
1084         return getDomNodeOrDie().getId();
1085     }
1086 
1087     /**
1088      * Sets the id value for this element.
1089      * @param newId the newId value for this element
1090      */
1091     @JsxSetter
1092     public void setId(final String newId) {
1093         getDomNodeOrDie().setId(newId);
1094     }
1095 
1096     /**
1097      * Removes the specified attribute.
1098      * @param attribute the attribute to remove
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      * Gets the scrollTop value for this element.
1109      * @return the scrollTop value for this element
1110      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534618.aspx">MSDN documentation</a>
1111      */
1112     @JsxGetter
1113     public int getScrollTop() {
1114         // It's easier to perform these checks and adjustments in the getter, rather than in the setter,
1115         // because modifying the CSS style of the element is supposed to affect the attribute value.
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      * Sets the scrollTop value for this element.
1131      * @param scroll the scrollTop value for this element
1132      */
1133     @JsxSetter
1134     public void setScrollTop(final int scroll) {
1135         scrollTop_ = scroll;
1136     }
1137 
1138     /**
1139      * Gets the scrollLeft value for this element.
1140      * @return the scrollLeft value for this element
1141      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534617.aspx">MSDN documentation</a>
1142      */
1143     @JsxGetter
1144     public int getScrollLeft() {
1145         // It's easier to perform these checks and adjustments in the getter, rather than in the setter,
1146         // because modifying the CSS style of the element is supposed to affect the attribute value.
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      * Sets the scrollLeft value for this element.
1162      * @param scroll the scrollLeft value for this element
1163      */
1164     @JsxSetter
1165     public void setScrollLeft(final int scroll) {
1166         scrollLeft_ = scroll;
1167     }
1168 
1169     /**
1170      * Gets the scrollHeight for this element.
1171      * @return at the moment the same as client height
1172      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534615.aspx">MSDN documentation</a>
1173      */
1174     @JsxGetter
1175     public int getScrollHeight() {
1176         return getClientHeight();
1177     }
1178 
1179     /**
1180      * Gets the scrollWidth for this element.
1181      * @return a dummy value of 10
1182      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534619.aspx">MSDN documentation</a>
1183      */
1184     @JsxGetter
1185     public int getScrollWidth() {
1186         return getClientWidth();
1187     }
1188 
1189     /**
1190      * Returns the style object for this element.
1191      * @return the style object for this element
1192      */
1193     protected CSSStyleDeclaration getStyle() {
1194         return style_;
1195     }
1196 
1197     /**
1198      * Sets the styles for this element.
1199      * @param style the style of the element
1200      */
1201     protected void setStyle(final String style) {
1202         getStyle().setCssText(style);
1203     }
1204 
1205     /**
1206      * Scrolls to a particular set of coordinates inside a given element.
1207      * @param x the horizontal pixel value that you want to scroll to
1208      * @param y the vertical pixel value that you want to scroll to
1209      */
1210     @JsxFunction
1211     public void scroll(final Scriptable x, final Scriptable y) {
1212         scrollTo(x, y);
1213     }
1214 
1215     /**
1216      * Scrolls the element by the given amount.
1217      * @param x the horizontal pixel value that you want to scroll by
1218      * @param y the vertical pixel value that you want to scroll by
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      * Scrolls to a particular set of coordinates inside a given element.
1273      * @param x the horizontal pixel value that you want to scroll to
1274      * @param y the vertical pixel value that you want to scroll to
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      * Implement the {@code scrollIntoView()} JavaScript function but don't actually do
1307      * anything. The requirement
1308      * is just to prevent scripts that call that method from failing
1309      */
1310     @JsxFunction
1311     public void scrollIntoView() {
1312         // do nothing at the moment, only trigger the scroll event
1313 
1314         // we do not really handle scrollable elements (we are headless)
1315         // we trigger the event for the whole parent tree (to inform all)
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      * Implement the {@code scrollIntoViewIfNeeded()} JavaScript function but don't actually do
1329      * anything.
1330      */
1331     @JsxFunction({CHROME, EDGE})
1332     public void scrollIntoViewIfNeeded() {
1333         /* do nothing at the moment */
1334     }
1335 
1336     /**
1337      * {@inheritDoc}
1338      */
1339     @Override
1340     @JsxGetter
1341     public String getPrefix() {
1342         return super.getPrefix();
1343     }
1344 
1345     /**
1346      * {@inheritDoc}
1347      */
1348     @Override
1349     @JsxGetter
1350     public String getLocalName() {
1351         return super.getLocalName();
1352     }
1353 
1354     /**
1355      * {@inheritDoc}
1356      */
1357     @Override
1358     @JsxGetter
1359     public String getNamespaceURI() {
1360         return super.getNamespaceURI();
1361     }
1362 
1363     /**
1364      * Returns the {@code onbeforecopy} event handler for this element.
1365      * @return the {@code onbeforecopy} event handler for this element
1366      */
1367     @JsxGetter({CHROME, EDGE})
1368     public Function getOnbeforecopy() {
1369         return getEventHandler(Event.TYPE_BEFORECOPY);
1370     }
1371 
1372     /**
1373      * Sets the {@code onbeforecopy} event handler for this element.
1374      * @param onbeforecopy the {@code onbeforecopy} event handler for this element
1375      */
1376     @JsxSetter({CHROME, EDGE})
1377     public void setOnbeforecopy(final Object onbeforecopy) {
1378         setEventHandler(Event.TYPE_BEFORECOPY, onbeforecopy);
1379     }
1380 
1381     /**
1382      * Returns the {@code onbeforecut} event handler for this element.
1383      * @return the {@code onbeforecut} event handler for this element
1384      */
1385     @JsxGetter({CHROME, EDGE})
1386     public Function getOnbeforecut() {
1387         return getEventHandler(Event.TYPE_BEFORECUT);
1388     }
1389 
1390     /**
1391      * Sets the {@code onbeforecut} event handler for this element.
1392      * @param onbeforecut the {@code onbeforecut} event handler for this element
1393      */
1394     @JsxSetter({CHROME, EDGE})
1395     public void setOnbeforecut(final Object onbeforecut) {
1396         setEventHandler(Event.TYPE_BEFORECUT, onbeforecut);
1397     }
1398 
1399     /**
1400      * Returns the {@code onbeforepaste} event handler for this element.
1401      * @return the {@code onbeforepaste} event handler for this element
1402      */
1403     @JsxGetter({CHROME, EDGE})
1404     public Function getOnbeforepaste() {
1405         return getEventHandler(Event.TYPE_BEFOREPASTE);
1406     }
1407 
1408     /**
1409      * Sets the {@code onbeforepaste} event handler for this element.
1410      * @param onbeforepaste the {@code onbeforepaste} event handler for this element
1411      */
1412     @JsxSetter({CHROME, EDGE})
1413     public void setOnbeforepaste(final Object onbeforepaste) {
1414         setEventHandler(Event.TYPE_BEFOREPASTE, onbeforepaste);
1415     }
1416 
1417     /**
1418      * Returns the {@code onsearch} event handler for this element.
1419      * @return the {@code onsearch} event handler for this element
1420      */
1421     @JsxGetter({CHROME, EDGE})
1422     public Function getOnsearch() {
1423         return getEventHandler(Event.TYPE_SEARCH);
1424     }
1425 
1426     /**
1427      * Sets the {@code onsearch} event handler for this element.
1428      * @param onsearch the {@code onsearch} event handler for this element
1429      */
1430     @JsxSetter({CHROME, EDGE})
1431     public void setOnsearch(final Object onsearch) {
1432         setEventHandler(Event.TYPE_SEARCH, onsearch);
1433     }
1434 
1435     /**
1436      * Returns the {@code onwebkitfullscreenchange} event handler for this element.
1437      * @return the {@code onwebkitfullscreenchange} event handler for this element
1438      */
1439     @JsxGetter({CHROME, EDGE})
1440     public Function getOnwebkitfullscreenchange() {
1441         return getEventHandler(Event.TYPE_WEBKITFULLSCREENCHANGE);
1442     }
1443 
1444     /**
1445      * Sets the {@code onwebkitfullscreenchange} event handler for this element.
1446      * @param onwebkitfullscreenchange the {@code onwebkitfullscreenchange} event handler for this element
1447      */
1448     @JsxSetter({CHROME, EDGE})
1449     public void setOnwebkitfullscreenchange(final Object onwebkitfullscreenchange) {
1450         setEventHandler(Event.TYPE_WEBKITFULLSCREENCHANGE, onwebkitfullscreenchange);
1451     }
1452 
1453     /**
1454      * Returns the {@code onwebkitfullscreenerror} event handler for this element.
1455      * @return the {@code onwebkitfullscreenerror} event handler for this element
1456      */
1457     @JsxGetter({CHROME, EDGE})
1458     public Function getOnwebkitfullscreenerror() {
1459         return getEventHandler(Event.TYPE_WEBKITFULLSCREENERROR);
1460     }
1461 
1462     /**
1463      * Sets the {@code onwebkitfullscreenerror} event handler for this element.
1464      * @param onwebkitfullscreenerror the {@code onwebkitfullscreenerror} event handler for this element
1465      */
1466     @JsxSetter({CHROME, EDGE})
1467     public void setOnwebkitfullscreenerror(final Object onwebkitfullscreenerror) {
1468         setEventHandler(Event.TYPE_WEBKITFULLSCREENERROR, onwebkitfullscreenerror);
1469     }
1470 
1471     /**
1472      * Returns the {@code onwheel} event handler for this element.
1473      * @return the {@code onwheel} event handler for this element
1474      */
1475     public Function getOnwheel() {
1476         return getEventHandler(Event.TYPE_WHEEL);
1477     }
1478 
1479     /**
1480      * Sets the {@code onwheel} event handler for this element.
1481      * @param onwheel the {@code onwheel} event handler for this element
1482      */
1483     public void setOnwheel(final Object onwheel) {
1484         setEventHandler(Event.TYPE_WHEEL, onwheel);
1485     }
1486 
1487     /**
1488      * {@inheritDoc}
1489      */
1490     @Override
1491     @JsxFunction
1492     public void remove() {
1493         super.remove();
1494     }
1495 
1496     /**
1497      * Mock for the moment.
1498      * @param retargetToElement if true, all events are targeted directly to this element;
1499      *        if false, events can also fire at descendants of this element
1500      */
1501     @JsxFunction({FF, FF_ESR})
1502     public void setCapture(final boolean retargetToElement) {
1503         // empty
1504     }
1505 
1506     /**
1507      * Mock for the moment.
1508      */
1509     @JsxFunction({FF, FF_ESR})
1510     public void releaseCapture() {
1511         // nothing to do
1512     }
1513 
1514     /**
1515      * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
1516      * just before this ChildNode.
1517      * @param context the context
1518      * @param scope the scope
1519      * @param thisObj this object
1520      * @param args the arguments
1521      * @param function the function
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      * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent,
1531      * just after this ChildNode.
1532      * @param context the context
1533      * @param scope the scope
1534      * @param thisObj this object
1535      * @param args the arguments
1536      * @param function the function
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      * Replaces the node with a set of Node or DOMString objects.
1546      * @param context the context
1547      * @param scope the scope
1548      * @param thisObj this object
1549      * @param args the arguments
1550      * @param function the function
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      * Returns true if the element would be selected by the specified selector string; otherwise, returns false.
1560      * @param context the JavaScript context
1561      * @param scope the scope
1562      * @param thisObj the scriptable
1563      * @param args the arguments passed into the method
1564      * @param function the function
1565      * @return the value
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      * Returns true if the element would be selected by the specified selector string; otherwise, returns false.
1590      * @param context the JavaScript context
1591      * @param scope the scope
1592      * @param thisObj the scriptable
1593      * @param args the arguments passed into the method
1594      * @param function the function
1595      * @return the value
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      * Returns true if the element would be selected by the specified selector string; otherwise, returns false.
1605      * @param context the JavaScript context
1606      * @param scope the scope
1607      * @param thisObj the scriptable
1608      * @param args the arguments passed into the method
1609      * @param function the function
1610      * @return the value
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      * Traverses the element and its parents (heading toward the document root) until it finds a node
1620      * that matches the specified CSS selector.
1621      * @param context the context
1622      * @param scope the scope
1623      * @param thisObj this object
1624      * @param args the arguments
1625      * @param function the function
1626      * @return the found element or null
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      * The <code>toggleAttribute()</code> method of the Element interface toggles a
1656      * Boolean attribute (removing it if it is present and adding it if it is not
1657      * present) on the given element. If <code>force</code> is <code>true</code>, adds
1658      * boolean attribute with <code>name</code>. If <code>force</code> is <code>false</code>,
1659      * removes attribute with <code>name</code>.
1660      *
1661      * @param name the name of the attribute to be toggled.
1662      *        The attribute name is automatically converted to all lower-case when toggleAttribute()
1663      *        is called on an HTML element in an HTML document.
1664      * @param force if true, the toggleAttribute method adds an attribute named name
1665      * @return true if attribute name is eventually present, and false otherwise
1666      * @see <a href=
1667      *      "https://developer.mozilla.org/en-US/docs/Web/API/Element/toggleAttribute">Element.toggleAttribute()</a>
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      * Inserts a set of Node objects or string objects after the last child of the Element.
1689      * String objects are inserted as equivalent Text nodes.
1690      * @param context the context
1691      * @param scope the scope
1692      * @param thisObj this object
1693      * @param args the arguments
1694      * @param function the function
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      * Inserts a set of Node objects or string objects before the first child of the Element.
1708      * String objects are inserted as equivalent Text nodes.
1709      * @param context the context
1710      * @param scope the scope
1711      * @param thisObj this object
1712      * @param args the arguments
1713      * @param function the function
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      * Replaces the existing children of a Node with a specified new set of children.
1727      * These can be string or Node objects.
1728      * @param context the context
1729      * @param scope the scope
1730      * @param thisObj this object
1731      * @param args the arguments
1732      * @param function the function
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 }