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