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