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