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