1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host;
16
17 import static org.htmlunit.BrowserVersionFeatures.EVENT_SCROLL_UIEVENT;
18 import static org.htmlunit.BrowserVersionFeatures.JS_INNER_HTML_PROCESSING_INSTRUCTION_NO_QUESTION_MARK;
19 import static org.htmlunit.BrowserVersionFeatures.JS_INNER_HTML_VOID_ELEMENT_EMPTY;
20 import static org.htmlunit.BrowserVersionFeatures.JS_INNER_HTML_XHTML_RAW_SCRIPT_STYLE;
21 import static org.htmlunit.html.DomElement.ATTRIBUTE_NOT_DEFINED;
22 import static org.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
23 import static org.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
24 import static org.htmlunit.javascript.configuration.SupportedBrowser.FF;
25 import static org.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
26
27 import java.io.IOException;
28 import java.io.Serializable;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.Objects;
33 import java.util.function.Predicate;
34 import java.util.regex.Pattern;
35
36 import org.apache.commons.logging.LogFactory;
37 import org.htmlunit.SgmlPage;
38 import org.htmlunit.corejs.javascript.BaseFunction;
39 import org.htmlunit.corejs.javascript.Context;
40 import org.htmlunit.corejs.javascript.Function;
41 import org.htmlunit.corejs.javascript.NativeObject;
42 import org.htmlunit.corejs.javascript.Scriptable;
43 import org.htmlunit.corejs.javascript.ScriptableObject;
44 import org.htmlunit.corejs.javascript.TopLevel;
45 import org.htmlunit.corejs.javascript.VarScope;
46 import org.htmlunit.corejs.javascript.WithScope;
47 import org.htmlunit.css.ComputedCssStyleDeclaration;
48 import org.htmlunit.css.ElementCssStyleDeclaration;
49 import org.htmlunit.cssparser.parser.CSSException;
50 import org.htmlunit.html.DomAttr;
51 import org.htmlunit.html.DomCDataSection;
52 import org.htmlunit.html.DomCharacterData;
53 import org.htmlunit.html.DomComment;
54 import org.htmlunit.html.DomElement;
55 import org.htmlunit.html.DomNode;
56 import org.htmlunit.html.DomProcessingInstruction;
57 import org.htmlunit.html.DomText;
58 import org.htmlunit.html.HtmlElement;
59 import org.htmlunit.html.HtmlElement.DisplayStyle;
60 import org.htmlunit.html.HtmlScript;
61 import org.htmlunit.html.HtmlStyle;
62 import org.htmlunit.html.HtmlTemplate;
63 import org.htmlunit.html.XHtmlPage;
64 import org.htmlunit.html.serializer.XmlMarkupSerializer;
65 import org.htmlunit.javascript.HtmlUnitScriptable;
66 import org.htmlunit.javascript.JavaScriptEngine;
67 import org.htmlunit.javascript.configuration.JsxClass;
68 import org.htmlunit.javascript.configuration.JsxConstructor;
69 import org.htmlunit.javascript.configuration.JsxFunction;
70 import org.htmlunit.javascript.configuration.JsxGetter;
71 import org.htmlunit.javascript.configuration.JsxSetter;
72 import org.htmlunit.javascript.host.css.CSSStyleDeclaration;
73 import org.htmlunit.javascript.host.dom.Attr;
74 import org.htmlunit.javascript.host.dom.DOMException;
75 import org.htmlunit.javascript.host.dom.DOMTokenList;
76 import org.htmlunit.javascript.host.dom.Node;
77 import org.htmlunit.javascript.host.dom.NodeList;
78 import org.htmlunit.javascript.host.event.Event;
79 import org.htmlunit.javascript.host.event.EventHandler;
80 import org.htmlunit.javascript.host.event.UIEvent;
81 import org.htmlunit.javascript.host.html.HTMLCollection;
82 import org.htmlunit.javascript.host.html.HTMLElement;
83 import org.htmlunit.javascript.host.html.HTMLElement.ProxyDomNode;
84 import org.htmlunit.javascript.host.html.HTMLScriptElement;
85 import org.htmlunit.javascript.host.html.HTMLStyleElement;
86 import org.htmlunit.javascript.host.html.HTMLTemplateElement;
87 import org.htmlunit.util.StringUtils;
88 import org.htmlunit.util.XmlUtils;
89 import org.htmlunit.xml.XmlPage;
90 import org.xml.sax.SAXException;
91
92
93
94
95
96
97
98
99
100
101
102
103
104 @JsxClass(domClass = DomElement.class)
105 public class Element extends Node {
106
107 static final String POSITION_BEFORE_BEGIN = "beforebegin";
108 static final String POSITION_AFTER_BEGIN = "afterbegin";
109 static final String POSITION_BEFORE_END = "beforeend";
110 static final String POSITION_AFTER_END = "afterend";
111
112 private static final Pattern CLASS_NAMES_SPLIT_PATTERN = Pattern.compile("\\s");
113 private static final Pattern PRINT_NODE_PATTERN = Pattern.compile(" {2}");
114
115 private enum TextEscape { NONE, XML, HTML }
116
117 private NamedNodeMap attributes_;
118 private Map<String, HTMLCollection> elementsByTagName_;
119 private int scrollLeft_;
120 private int scrollTop_;
121 private CSSStyleDeclaration style_;
122
123
124
125
126 @Override
127 @JsxConstructor
128 public void jsConstructor() {
129 super.jsConstructor();
130 }
131
132
133
134
135
136
137 @Override
138 public void setDomNode(final DomNode domNode) {
139 super.setDomNode(domNode);
140
141 final Window window = getWindow();
142 setParentScope(new WithScope(getTopLevelScope(getParentScope()), window.getDocument()));
143
144 style_ = new CSSStyleDeclaration(this, new ElementCssStyleDeclaration(getDomNodeOrDie()));
145
146
147
148 final DomElement htmlElt = (DomElement) domNode;
149 for (final DomAttr attr : htmlElt.getAttributesMap().values()) {
150 final String eventName = StringUtils.toRootLowerCase(attr.getName());
151 if (eventName.startsWith("on")) {
152 createEventHandler(eventName.substring(2), attr.getValue());
153 }
154 }
155 }
156
157
158
159
160
161
162
163 protected void createEventHandler(final String eventName, final String attrValue) {
164 final DomElement htmlElt = getDomNodeOrDie();
165
166
167 final BaseFunction eventHandler = new EventHandler(getParentScope(), htmlElt, eventName, attrValue);
168 eventHandler.setPrototype(ScriptableObject.getClassPrototype(getParentScope(), "Function"));
169
170 setEventHandler(eventName, eventHandler);
171 }
172
173
174
175
176
177
178 @JsxGetter
179 public String getTagName() {
180 return getNodeName();
181 }
182
183
184
185
186
187
188
189 @Override
190 @JsxGetter
191 public NamedNodeMap getAttributes() {
192 if (attributes_ == null) {
193 attributes_ = createAttributesObject();
194 }
195 return attributes_;
196 }
197
198
199
200
201
202
203 protected NamedNodeMap createAttributesObject() {
204 return new NamedNodeMap(getDomNodeOrDie());
205 }
206
207
208
209
210
211
212
213 @JsxFunction
214 public String getAttribute(final String attributeName) {
215 String value = getDomNodeOrDie().getAttribute(attributeName);
216
217 if (ATTRIBUTE_NOT_DEFINED == value) {
218 value = null;
219 }
220
221 return value;
222 }
223
224
225
226
227
228
229
230 @JsxFunction
231 public void setAttribute(final String name, final String value) {
232 getDomNodeOrDie().setAttribute(name, value);
233 }
234
235
236
237
238
239
240
241 @JsxFunction
242 public HTMLCollection getElementsByTagName(final String tagName) {
243 if (elementsByTagName_ == null) {
244 elementsByTagName_ = new HashMap<>();
245 }
246
247 final String searchTagName;
248 final boolean caseSensitive;
249 final DomNode dom = getDomNodeOrNull();
250 if (dom == null) {
251 searchTagName = StringUtils.toRootLowerCase(tagName);
252 caseSensitive = false;
253 }
254 else {
255 final SgmlPage page = dom.getPage();
256 if (page != null && page.hasCaseSensitiveTagNames()) {
257 searchTagName = tagName;
258 caseSensitive = true;
259 }
260 else {
261 searchTagName = StringUtils.toRootLowerCase(tagName);
262 caseSensitive = false;
263 }
264 }
265
266 HTMLCollection collection = elementsByTagName_.get(searchTagName);
267 if (collection != null) {
268 return collection;
269 }
270
271 final DomNode node = getDomNodeOrDie();
272 collection = new HTMLCollection(node, false);
273 if (StringUtils.equalsChar('*', tagName)) {
274 collection.setIsMatchingPredicate((Predicate<DomNode> & Serializable) nodeToMatch -> true);
275 }
276 else {
277 collection.setIsMatchingPredicate(
278 (Predicate<DomNode> & Serializable) nodeToMatch -> {
279 if (caseSensitive) {
280 return searchTagName.equals(nodeToMatch.getNodeName());
281 }
282 return searchTagName.equalsIgnoreCase(nodeToMatch.getNodeName());
283 });
284 }
285
286 elementsByTagName_.put(tagName, collection);
287
288 return collection;
289 }
290
291
292
293
294
295
296
297 @JsxFunction
298 public HtmlUnitScriptable getAttributeNode(final String name) {
299 final Map<String, DomAttr> attributes = getDomNodeOrDie().getAttributesMap();
300 for (final DomAttr attr : attributes.values()) {
301 if (attr.getName().equals(name)) {
302 return attr.getScriptableObject();
303 }
304 }
305 return null;
306 }
307
308
309
310
311
312
313
314
315 @JsxFunction
316 public HTMLCollection getElementsByTagNameNS(final Object namespaceURI, final String localName) {
317 final HTMLCollection elements = new HTMLCollection(getDomNodeOrDie(), false);
318 elements.setIsMatchingPredicate(
319 (Predicate<DomNode> & Serializable)
320 node -> ("*".equals(namespaceURI) || Objects.equals(namespaceURI, node.getNamespaceURI()))
321 && ("*".equals(localName) || Objects.equals(localName, node.getLocalName())));
322 return elements;
323 }
324
325
326
327
328
329
330
331 @JsxFunction
332 public boolean hasAttribute(final String name) {
333 return getDomNodeOrDie().hasAttribute(name);
334 }
335
336
337
338
339 @Override
340 @JsxFunction
341 public boolean hasAttributes() {
342 return super.hasAttributes();
343 }
344
345
346
347
348 @Override
349 public DomElement getDomNodeOrDie() {
350 return (DomElement) super.getDomNodeOrDie();
351 }
352
353
354
355
356
357
358 @JsxFunction
359 public void removeAttribute(final String name) {
360 getDomNodeOrDie().removeAttribute(name);
361 }
362
363
364
365
366
367
368
369 @JsxFunction
370 public DOMRect getBoundingClientRect() {
371 final DOMRect textRectangle = new DOMRect(1, 1, 0, 0);
372 textRectangle.setParentScope(getTopLevelScope(getParentScope()));
373 textRectangle.setPrototype(getPrototype(textRectangle.getClass()));
374 return textRectangle;
375 }
376
377
378
379
380 @Override
381 @JsxGetter
382 public int getChildElementCount() {
383 return getDomNodeOrDie().getChildElementCount();
384 }
385
386
387
388
389 @Override
390 @JsxGetter
391 public Element getFirstElementChild() {
392 return super.getFirstElementChild();
393 }
394
395
396
397
398 @Override
399 @JsxGetter
400 public Element getLastElementChild() {
401 return super.getLastElementChild();
402 }
403
404
405
406
407
408
409 @JsxGetter
410 public Element getNextElementSibling() {
411 final DomElement child = getDomNodeOrDie().getNextElementSibling();
412 if (child != null) {
413 return child.getScriptableObject();
414 }
415 return null;
416 }
417
418
419
420
421
422
423 @JsxGetter
424 public Element getPreviousElementSibling() {
425 final DomElement child = getDomNodeOrDie().getPreviousElementSibling();
426 if (child != null) {
427 return child.getScriptableObject();
428 }
429 return null;
430 }
431
432
433
434
435
436
437
438 @Override
439 public Element getParentElement() {
440 Node parent = getParent();
441 while (parent != null && !(parent instanceof Element)) {
442 parent = parent.getParent();
443 }
444 return (Element) parent;
445 }
446
447
448
449
450 @Override
451 @JsxGetter
452 public HTMLCollection getChildren() {
453 return super.getChildren();
454 }
455
456
457
458
459
460
461 @JsxGetter
462 public DOMTokenList getClassList() {
463 return new DOMTokenList(this, "class");
464 }
465
466
467
468
469
470
471
472
473
474 @JsxFunction
475 public String getAttributeNS(final String namespaceURI, final String localName) {
476 final String value = getDomNodeOrDie().getAttributeNS(namespaceURI, localName);
477 if (ATTRIBUTE_NOT_DEFINED == value) {
478 return null;
479 }
480 return value;
481 }
482
483
484
485
486
487
488
489
490 @JsxFunction
491 public boolean hasAttributeNS(final String namespaceURI, final String localName) {
492 return getDomNodeOrDie().hasAttributeNS(namespaceURI, localName);
493 }
494
495
496
497
498
499
500
501
502 @JsxFunction
503 public void setAttributeNS(final String namespaceURI, final String qualifiedName, final String value) {
504 getDomNodeOrDie().setAttributeNS(namespaceURI, qualifiedName, value);
505 }
506
507
508
509
510
511
512
513 @JsxFunction
514 public void removeAttributeNS(final String namespaceURI, final String localName) {
515 getDomNodeOrDie().removeAttributeNS(namespaceURI, localName);
516 }
517
518
519
520
521
522
523
524 @JsxFunction
525 public Attr setAttributeNode(final Attr newAtt) {
526 final String name = newAtt.getName();
527
528 final NamedNodeMap nodes = getAttributes();
529 final Attr replacedAtt = (Attr) nodes.getNamedItemWithoutSytheticClassAttr(name);
530 if (replacedAtt != null) {
531 replacedAtt.detachFromParent();
532 }
533
534 final DomAttr newDomAttr = newAtt.getDomNodeOrDie();
535 getDomNodeOrDie().setAttributeNode(newDomAttr);
536 return replacedAtt;
537 }
538
539
540
541
542
543
544
545 @JsxFunction
546 public NodeList querySelectorAll(final String selectors) {
547 try {
548 final DomNode domNode = getDomNodeOrDie();
549 return NodeList.staticNodeList(getParentScope(), domNode.querySelectorAll(selectors, domNode));
550 }
551 catch (final CSSException e) {
552 throw JavaScriptEngine.asJavaScriptException(
553 getWindow(),
554 "An invalid or illegal selector was specified (selector: '"
555 + selectors + "' error: " + e.getMessage() + ").",
556 DOMException.SYNTAX_ERR);
557 }
558 }
559
560
561
562
563
564
565
566
567 @JsxFunction
568 public Node querySelector(final String selectors) {
569 try {
570 final DomNode domNode = getDomNodeOrDie();
571 final DomNode node = domNode.querySelector(selectors, domNode);
572 if (node != null) {
573 return node.getScriptableObject();
574 }
575 return null;
576 }
577 catch (final CSSException e) {
578 throw JavaScriptEngine.asJavaScriptException(
579 getWindow(),
580 "An invalid or illegal selector was specified (selector: '"
581 + selectors + "' error: " + e.getMessage() + ").",
582 DOMException.SYNTAX_ERR);
583 }
584 }
585
586
587
588
589
590
591 @JsxGetter(propertyName = "className")
592 public String getClassName_js() {
593 return getDomNodeOrDie().getAttributeDirect("class");
594 }
595
596
597
598
599
600
601 @JsxSetter(propertyName = "className")
602 public void setClassName_js(final String className) {
603 getDomNodeOrDie().setAttribute("class", className);
604 }
605
606
607
608
609
610
611 @JsxGetter
612 public int getClientHeight() {
613 final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
614 return style.getCalculatedHeight(false, true);
615 }
616
617
618
619
620
621
622 @JsxGetter
623 public int getClientWidth() {
624 final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
625 return style.getCalculatedWidth(false, true);
626 }
627
628
629
630
631
632
633 @JsxGetter
634 public int getClientLeft() {
635 final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
636 return style.getBorderLeftValue();
637 }
638
639
640
641
642
643
644 @JsxGetter
645 public int getClientTop() {
646 final ComputedCssStyleDeclaration style = getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
647 return style.getBorderTopValue();
648 }
649
650
651
652
653
654
655
656
657 @JsxFunction
658 public HtmlUnitScriptable getAttributeNodeNS(final String namespaceURI, final String localName) {
659 return getDomNodeOrDie().getAttributeNodeNS(namespaceURI, localName).getScriptableObject();
660 }
661
662
663
664
665
666
667
668 @JsxFunction
669 public HTMLCollection getElementsByClassName(final String className) {
670 final DomElement elt = getDomNodeOrDie();
671 final String[] classNames = CLASS_NAMES_SPLIT_PATTERN.split(className, 0);
672
673 final HTMLCollection elements = new HTMLCollection(elt, true);
674
675 elements.setIsMatchingPredicate(
676 (Predicate<DomNode> & Serializable)
677 node -> {
678 if (!(node instanceof HtmlElement)) {
679 return false;
680 }
681 String classAttribute = ((HtmlElement) node).getAttributeDirect("class");
682 if (ATTRIBUTE_NOT_DEFINED == classAttribute) {
683 return false;
684 }
685
686 classAttribute = " " + classAttribute + " ";
687 for (final String aClassName : classNames) {
688 if (!classAttribute.contains(" " + aClassName + " ")) {
689 return false;
690 }
691 }
692 return true;
693 });
694
695 return elements;
696 }
697
698
699
700
701
702
703
704 @JsxFunction
705 public DOMRectList getClientRects() {
706 final TopLevel topScope = getTopLevelScope(getParentScope());
707 final DOMRectList rectList = new DOMRectList();
708 rectList.setParentScope(topScope);
709 rectList.setPrototype(getPrototype(rectList.getClass()));
710
711 if (!isDisplayNone() && getDomNodeOrDie().isAttachedToPage()) {
712 final DOMRect rect = new DOMRect(0, 0, 1, 1);
713 rect.setParentScope(topScope);
714 rect.setPrototype(getPrototype(rect.getClass()));
715 rectList.add(rect);
716 }
717
718 return rectList;
719 }
720
721
722
723
724
725
726
727 @JsxFunction
728 public Scriptable getAttributeNames() {
729 final org.w3c.dom.NamedNodeMap attributes = getDomNodeOrDie().getAttributes();
730
731 if (attributes.getLength() == 0) {
732 return JavaScriptEngine.newArray(getParentScope(), 0);
733 }
734
735 final ArrayList<String> res = new ArrayList<>();
736 for (int i = 0; i < attributes.getLength(); i++) {
737 res.add(attributes.item(i).getNodeName());
738 }
739
740 return JavaScriptEngine.newArray(getParentScope(), res.toArray());
741 }
742
743
744
745
746
747
748 protected final boolean isDisplayNone() {
749 Element element = this;
750 while (element != null) {
751 final CSSStyleDeclaration style = element.getWindow().getComputedStyle(element, null);
752 final String display = style.getDisplay();
753 if (DisplayStyle.NONE.value().equals(display)) {
754 return true;
755 }
756 element = element.getParentElement();
757 }
758 return false;
759 }
760
761
762
763
764
765
766
767
768
769
770 @JsxFunction
771 public Node insertAdjacentElement(final String where, final Object insertedElement) {
772 if (insertedElement instanceof Node insertedElementNode) {
773 final DomNode childNode = insertedElementNode.getDomNodeOrDie();
774 final Object[] values = getInsertAdjacentLocation(where);
775 final DomNode node = (DomNode) values[0];
776 final boolean append = ((Boolean) values[1]).booleanValue();
777
778 if (append) {
779 node.appendChild(childNode);
780 }
781 else {
782 node.insertBefore(childNode);
783 }
784 return insertedElementNode;
785 }
786 throw JavaScriptEngine.reportRuntimeError("Passed object is not an element: " + insertedElement);
787 }
788
789
790
791
792
793
794
795
796
797 @JsxFunction
798 public void insertAdjacentText(final String where, final String text) {
799 final Object[] values = getInsertAdjacentLocation(where);
800 final DomNode node = (DomNode) values[0];
801 final boolean append = ((Boolean) values[1]).booleanValue();
802
803 final DomText domText = new DomText(node.getPage(), text);
804
805 if (append) {
806 node.appendChild(domText);
807 }
808 else {
809 node.insertBefore(domText);
810 }
811 }
812
813
814
815
816
817
818
819
820
821
822
823 private Object[] getInsertAdjacentLocation(final String where) {
824 final DomNode currentNode = getDomNodeOrDie();
825 final DomNode node;
826 final boolean append;
827
828
829 if (POSITION_AFTER_BEGIN.equalsIgnoreCase(where)) {
830 if (currentNode.getFirstChild() == null) {
831
832 node = currentNode;
833 append = true;
834 }
835 else {
836
837 node = currentNode.getFirstChild();
838 append = false;
839 }
840 }
841 else if (POSITION_BEFORE_BEGIN.equalsIgnoreCase(where)) {
842
843 node = currentNode;
844 append = false;
845 }
846 else if (POSITION_BEFORE_END.equalsIgnoreCase(where)) {
847
848 node = currentNode;
849 append = true;
850 }
851 else if (POSITION_AFTER_END.equalsIgnoreCase(where)) {
852 if (currentNode.getNextSibling() == null) {
853
854 node = currentNode.getParentNode();
855 append = true;
856 }
857 else {
858
859 node = currentNode.getNextSibling();
860 append = false;
861 }
862 }
863 else {
864 throw JavaScriptEngine.reportRuntimeError("Illegal position value: \"" + where + "\"");
865 }
866
867 if (append) {
868 return new Object[] {node, Boolean.TRUE};
869 }
870 return new Object[] {node, Boolean.FALSE};
871 }
872
873
874
875
876
877
878
879
880
881 @JsxFunction
882 public void insertAdjacentHTML(final String position, final String text) {
883 final Object[] values = getInsertAdjacentLocation(position);
884 final DomNode domNode = (DomNode) values[0];
885 final boolean append = ((Boolean) values[1]).booleanValue();
886
887
888 final DomNode proxyDomNode = new ProxyDomNode(domNode.getPage(), domNode, append);
889 parseHtmlSnippet(proxyDomNode, text);
890 }
891
892
893
894
895
896
897
898
899
900
901 @JsxFunction
902 public static void moveBefore(final Context context, final VarScope scope,
903 final Scriptable thisObj, final Object[] args, final Function function) {
904 Node.moveBefore(context, scope, thisObj, args, function);
905 }
906
907
908
909
910
911
912
913 private static void parseHtmlSnippet(final DomNode target, final String source) {
914 try {
915 target.parseHtmlSnippet(source);
916 }
917 catch (final IOException | SAXException e) {
918 LogFactory.getLog(HtmlElement.class).error("Unexpected exception occurred while parsing HTML snippet", e);
919 throw JavaScriptEngine.reportRuntimeError("Unexpected exception occurred while parsing HTML snippet: "
920 + e.getMessage());
921 }
922 }
923
924
925
926
927
928
929 @JsxFunction
930 public String getHTML() {
931
932 return getInnerHTML();
933 }
934
935
936
937
938
939
940 @JsxGetter
941 public String getInnerHTML() {
942 try {
943 DomNode domNode = getDomNodeOrDie();
944 if (this instanceof HTMLTemplateElement) {
945 domNode = ((HtmlTemplate) getDomNodeOrDie()).getContent();
946 }
947 return getInnerHTML(domNode);
948 }
949 catch (final IllegalStateException e) {
950 throw JavaScriptEngine.typeError(e.getMessage());
951 }
952 }
953
954
955
956
957
958
959 @JsxSetter
960 public void setInnerHTML(final Object value) {
961 final DomElement domNode;
962 try {
963 domNode = getDomNodeOrDie();
964 }
965 catch (final IllegalStateException e) {
966 throw JavaScriptEngine.typeError(e.getMessage());
967 }
968
969 String html = null;
970 if (value != null) {
971 html = JavaScriptEngine.toString(value);
972 if (StringUtils.isEmptyString(html)) {
973 html = null;
974 }
975 }
976
977 try {
978 domNode.setInnerHtml(html);
979 }
980 catch (final IOException | SAXException e) {
981 LogFactory.getLog(HtmlElement.class).error("Unexpected exception occurred while parsing HTML snippet", e);
982 throw JavaScriptEngine.reportRuntimeError("Unexpected exception occurred while parsing HTML snippet: "
983 + e.getMessage());
984 }
985 }
986
987
988
989
990
991
992
993 protected String getInnerHTML(final DomNode domNode) {
994 if (isXmlSerialization(domNode)) {
995 if ((domNode instanceof HtmlScript || domNode instanceof HtmlStyle)
996 && getBrowserVersion().hasFeature(JS_INNER_HTML_XHTML_RAW_SCRIPT_STYLE)) {
997 return domNode.getTextContent();
998 }
999 return XmlMarkupSerializer.serializeChildren(domNode, getBrowserVersion());
1000 }
1001
1002 if (isEndTagForbidden() && getBrowserVersion().hasFeature(JS_INNER_HTML_VOID_ELEMENT_EMPTY)) {
1003 return "";
1004 }
1005
1006 final StringBuilder buf = new StringBuilder();
1007
1008 final String tagName = getTagName();
1009 final boolean isPlain = "SCRIPT".equals(tagName) || "STYLE".equals(tagName);
1010
1011 final TextEscape mode = domNode.getPage() instanceof XHtmlPage ? TextEscape.XML : TextEscape.HTML;
1012 final boolean rawText = domNode instanceof DomElement e && mode == TextEscape.HTML && isRawTextElement(e);
1013
1014
1015 printChildren(buf, domNode, !isPlain, isPlain || rawText ? TextEscape.NONE : mode);
1016 return buf.toString();
1017 }
1018
1019
1020
1021
1022
1023
1024
1025
1026 private static boolean isXmlSerialization(final DomNode domNode) {
1027 final SgmlPage page = domNode.getPage();
1028 return page instanceof XHtmlPage || page instanceof XmlPage;
1029 }
1030
1031
1032
1033
1034 private static boolean isRawTextElement(final DomElement element) {
1035 if (!(element instanceof HtmlElement) || element.getPage() instanceof XHtmlPage) {
1036 return false;
1037 }
1038 return switch (element.getLocalName()) {
1039 case "iframe", "xmp", "noembed", "noframes", "plaintext" -> true;
1040 case "noscript" -> element.getPage().getWebClient().isJavaScriptEnabled();
1041 default -> false;
1042 };
1043 }
1044
1045
1046
1047
1048
1049
1050
1051 @JsxGetter
1052 public String getOuterHTML() {
1053 final DomNode domNode = getDomNodeOrDie();
1054 if (isXmlSerialization(domNode)) {
1055 return XmlMarkupSerializer.serialize(domNode, getBrowserVersion());
1056 }
1057
1058 final StringBuilder buf = new StringBuilder();
1059
1060 printNode(buf, getDomNodeOrDie(), true, TextEscape.HTML);
1061 return buf.toString();
1062 }
1063
1064
1065
1066
1067
1068
1069 @JsxSetter
1070 public void setOuterHTML(final Object value) {
1071 final DomNode domNode = getDomNodeOrDie();
1072 final DomNode parent = domNode.getParentNode();
1073 if (parent == null) {
1074 return;
1075 }
1076
1077 if (value == null) {
1078 domNode.remove();
1079 return;
1080 }
1081
1082 final String valueStr = JavaScriptEngine.toString(value);
1083 if (valueStr.isEmpty()) {
1084 domNode.remove();
1085 return;
1086 }
1087
1088 final DomNode nextSibling = domNode.getNextSibling();
1089 domNode.remove();
1090
1091 final DomNode target;
1092 final boolean append;
1093 if (nextSibling != null) {
1094 target = nextSibling;
1095 append = false;
1096 }
1097 else {
1098 target = parent;
1099 append = true;
1100 }
1101
1102 final DomNode proxyDomNode = new ProxyDomNode(target.getPage(), target, append);
1103 parseHtmlSnippet(proxyDomNode, valueStr);
1104 }
1105
1106 protected final void printChildren(final StringBuilder builder, final DomNode node,
1107 final boolean html, final TextEscape escape) {
1108 if (node instanceof HtmlTemplate template) {
1109 for (final DomNode child : template.getContent().getChildren()) {
1110 printNode(builder, child, html, escape);
1111 }
1112 return;
1113 }
1114 for (final DomNode child : node.getChildren()) {
1115 printNode(builder, child, html, escape);
1116 }
1117 }
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127 protected void printNode(final StringBuilder builder, final DomNode node,
1128 final boolean html, final TextEscape escape) {
1129 if (node instanceof DomComment) {
1130 if (html) {
1131
1132 final String s = PRINT_NODE_PATTERN.matcher(node.getNodeValue()).replaceAll(" ");
1133 builder.append("<!--").append(s).append("-->");
1134 }
1135 }
1136 else if (node instanceof DomCDataSection) {
1137 builder.append("<![CDATA[").append(node.getNodeValue()).append("]]>");
1138 }
1139 else if (node instanceof DomCharacterData) {
1140 String s = node.getNodeValue();
1141 if (html) {
1142 s = switch (escape) {
1143 case HTML -> StringUtils.escapeHtmlText(s);
1144 case XML -> StringUtils.escapeXmlChars(s);
1145 case NONE -> s;
1146 };
1147 }
1148 builder.append(s);
1149 }
1150 else if (node instanceof DomProcessingInstruction pi) {
1151 builder.append("<?")
1152 .append(pi.getTarget())
1153 .append(" ")
1154 .append(pi.getData())
1155 .append(getBrowserVersion().hasFeature(JS_INNER_HTML_PROCESSING_INSTRUCTION_NO_QUESTION_MARK)
1156 ? ">" : "?>");
1157 }
1158 else if (html) {
1159 final DomElement element = (DomElement) node;
1160 final Element scriptObject = node.getScriptableObject();
1161 final String tag = serializationTagName(element);
1162
1163 Element htmlElement = null;
1164 if (scriptObject instanceof HTMLElement) {
1165 htmlElement = scriptObject;
1166 }
1167 final boolean xmlSerialization = node.getPage() instanceof XHtmlPage
1168 || node.getPage() instanceof XmlPage;
1169 builder.append('<').append(tag);
1170 for (final DomAttr attr : element.getAttributesMap().values()) {
1171 if (!attr.getSpecified()) {
1172 continue;
1173 }
1174
1175 final String name = attr.getName();
1176 final String value = xmlSerialization
1177 ? XmlUtils.escapeXmlAttributeValue(attr.getValue())
1178 : XmlUtils.escapeHtmlAttributeValue(attr.getValue());
1179 builder.append(' ').append(name).append("=\"").append(value).append('\"');
1180 }
1181 builder.append('>');
1182
1183 if (htmlElement != null && htmlElement.isEndTagForbidden()) {
1184
1185 return;
1186 }
1187
1188
1189 final TextEscape mode = node.getPage() instanceof XHtmlPage ? TextEscape.XML : TextEscape.HTML;
1190 final boolean rawTextHost = scriptObject instanceof HTMLScriptElement
1191 || scriptObject instanceof HTMLStyleElement;
1192
1193
1194 final boolean isHtml = mode == TextEscape.XML || !rawTextHost;
1195 final TextEscape childEscape = !isHtml || (mode == TextEscape.HTML && isRawTextElement(element))
1196 ? TextEscape.NONE
1197 : mode;
1198 printChildren(builder, node, isHtml, childEscape);
1199 builder.append("</").append(tag).append('>');
1200 }
1201 else {
1202 if (node instanceof HtmlElement element) {
1203 if (StringUtils.equalsChar('p', element.getTagName())) {
1204 int i = builder.length() - 1;
1205 while (i >= 0 && Character.isWhitespace(builder.charAt(i))) {
1206 i--;
1207 }
1208 builder.setLength(i + 1);
1209 builder.append('\n');
1210 }
1211 if (!"script".equals(element.getTagName())) {
1212 printChildren(builder, node, html, escape);
1213 }
1214 }
1215 }
1216 }
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228 private static String serializationTagName(final DomElement element) {
1229 final String prefix = element.getPrefix();
1230 final String name = prefix == null ? element.getLocalName() : prefix + ":" + element.getLocalName();
1231 final String qualifiedName = element.getQualifiedName();
1232 if (qualifiedName != null && qualifiedName.equalsIgnoreCase(name)) {
1233 return qualifiedName;
1234 }
1235 return name;
1236 }
1237
1238
1239
1240
1241
1242
1243
1244 protected boolean isEndTagForbidden() {
1245 return false;
1246 }
1247
1248
1249
1250
1251
1252
1253 @JsxGetter
1254 public String getId() {
1255 return getDomNodeOrDie().getId();
1256 }
1257
1258
1259
1260
1261
1262
1263 @JsxSetter
1264 public void setId(final String newId) {
1265 getDomNodeOrDie().setId(newId);
1266 }
1267
1268
1269
1270
1271
1272
1273 @JsxFunction
1274 public void removeAttributeNode(final Attr attribute) {
1275 final String name = attribute.getName();
1276 final String namespaceUri = attribute.getNamespaceURI();
1277 removeAttributeNS(namespaceUri, name);
1278 }
1279
1280
1281
1282
1283
1284
1285
1286 @JsxGetter
1287 public int getScrollTop() {
1288
1289
1290 if (scrollTop_ < 0) {
1291 scrollTop_ = 0;
1292 }
1293 else if (scrollTop_ > 0) {
1294 final ComputedCssStyleDeclaration style =
1295 getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
1296 if (!style.isScrollable(false)) {
1297 scrollTop_ = 0;
1298 }
1299 }
1300 return scrollTop_;
1301 }
1302
1303
1304
1305
1306
1307
1308 @JsxSetter
1309 public void setScrollTop(final int scroll) {
1310 scrollTop_ = scroll;
1311 }
1312
1313
1314
1315
1316
1317
1318
1319 @JsxGetter
1320 public int getScrollLeft() {
1321
1322
1323 if (scrollLeft_ < 0) {
1324 scrollLeft_ = 0;
1325 }
1326 else if (scrollLeft_ > 0) {
1327 final ComputedCssStyleDeclaration style =
1328 getWindow().getWebWindow().getComputedStyle(getDomNodeOrDie(), null);
1329 if (!style.isScrollable(true)) {
1330 scrollLeft_ = 0;
1331 }
1332 }
1333 return scrollLeft_;
1334 }
1335
1336
1337
1338
1339
1340
1341 @JsxSetter
1342 public void setScrollLeft(final int scroll) {
1343 scrollLeft_ = scroll;
1344 }
1345
1346
1347
1348
1349
1350
1351
1352
1353 @JsxGetter
1354 public int getScrollHeight() {
1355 return getClientHeight();
1356 }
1357
1358
1359
1360
1361
1362
1363
1364
1365 @JsxGetter
1366 public int getScrollWidth() {
1367 return getClientWidth();
1368 }
1369
1370
1371
1372
1373
1374
1375 protected CSSStyleDeclaration getStyle() {
1376 return style_;
1377 }
1378
1379
1380
1381
1382
1383
1384 protected void setStyle(final String style) {
1385 getStyle().setCssText(style);
1386 }
1387
1388
1389
1390
1391
1392
1393
1394 @JsxFunction
1395 public void scroll(final Scriptable x, final Scriptable y) {
1396 scrollTo(x, y);
1397 }
1398
1399
1400
1401
1402
1403
1404
1405 @JsxFunction
1406 public void scrollBy(final Scriptable x, final Scriptable y) {
1407 int xOff = 0;
1408 int yOff = 0;
1409 if (y != null) {
1410 xOff = JavaScriptEngine.toInt32(x);
1411 yOff = JavaScriptEngine.toInt32(y);
1412 }
1413 else {
1414 if (!(x instanceof NativeObject)) {
1415 throw JavaScriptEngine.typeError("eee");
1416 }
1417 if (x.has("left", x)) {
1418 xOff = JavaScriptEngine.toInt32(x.get("left", x));
1419 }
1420 if (x.has("top", x)) {
1421 yOff = JavaScriptEngine.toInt32(x.get("top", x));
1422 }
1423 }
1424
1425 setScrollLeft(getScrollLeft() + xOff);
1426 setScrollTop(getScrollTop() + yOff);
1427
1428 fireScrollEvent(this);
1429 }
1430
1431 private void fireScrollEvent(final Node node) {
1432 final Event event;
1433 if (getBrowserVersion().hasFeature(EVENT_SCROLL_UIEVENT)) {
1434 event = new UIEvent(node, Event.TYPE_SCROLL);
1435 }
1436 else {
1437 event = new Event(node, Event.TYPE_SCROLL);
1438 event.setCancelable(false);
1439 }
1440 event.setBubbles(false);
1441 node.fireEvent(event);
1442 }
1443
1444 private void fireScrollEvent(final Window window) {
1445 final Event event;
1446 if (getBrowserVersion().hasFeature(EVENT_SCROLL_UIEVENT)) {
1447 event = new UIEvent(window.getDocument(), Event.TYPE_SCROLL);
1448 }
1449 else {
1450 event = new Event(window.getDocument(), Event.TYPE_SCROLL);
1451 event.setCancelable(false);
1452 }
1453 window.fireEvent(event);
1454 }
1455
1456
1457
1458
1459
1460
1461
1462 @JsxFunction
1463 public void scrollTo(final Scriptable x, final Scriptable y) {
1464 int xOff;
1465 int yOff;
1466 if (y != null) {
1467 xOff = JavaScriptEngine.toInt32(x);
1468 yOff = JavaScriptEngine.toInt32(y);
1469 }
1470 else {
1471 if (!(x instanceof NativeObject)) {
1472 throw JavaScriptEngine.typeError("eee");
1473 }
1474
1475 xOff = getScrollLeft();
1476 yOff = getScrollTop();
1477 if (x.has("left", x)) {
1478 xOff = JavaScriptEngine.toInt32(x.get("left", x));
1479 }
1480 if (x.has("top", x)) {
1481 yOff = JavaScriptEngine.toInt32(x.get("top", x));
1482 }
1483 }
1484
1485 setScrollLeft(xOff);
1486 setScrollTop(yOff);
1487
1488 fireScrollEvent(this);
1489 }
1490
1491
1492
1493
1494
1495
1496 @JsxFunction
1497 public void scrollIntoView() {
1498
1499
1500
1501
1502 Node parent = getParent();
1503 while (parent != null) {
1504 if (parent instanceof HTMLElement) {
1505 fireScrollEvent(parent);
1506 }
1507
1508 parent = parent.getParent();
1509 }
1510 fireScrollEvent(getWindow());
1511 }
1512
1513
1514
1515
1516
1517 @JsxFunction({CHROME, EDGE})
1518 public void scrollIntoViewIfNeeded() {
1519
1520 }
1521
1522
1523
1524
1525 @Override
1526 @JsxGetter
1527 public String getPrefix() {
1528 return super.getPrefix();
1529 }
1530
1531
1532
1533
1534 @Override
1535 @JsxGetter
1536 public String getLocalName() {
1537 return super.getLocalName();
1538 }
1539
1540
1541
1542
1543 @Override
1544 @JsxGetter
1545 public String getNamespaceURI() {
1546 return super.getNamespaceURI();
1547 }
1548
1549
1550
1551
1552
1553
1554 @JsxGetter({CHROME, EDGE})
1555 public Function getOnbeforecopy() {
1556 return getEventHandler(Event.TYPE_BEFORECOPY);
1557 }
1558
1559
1560
1561
1562
1563
1564 @JsxSetter({CHROME, EDGE})
1565 public void setOnbeforecopy(final Object onbeforecopy) {
1566 setEventHandler(Event.TYPE_BEFORECOPY, onbeforecopy);
1567 }
1568
1569
1570
1571
1572
1573
1574 @JsxGetter({CHROME, EDGE})
1575 public Function getOnbeforecut() {
1576 return getEventHandler(Event.TYPE_BEFORECUT);
1577 }
1578
1579
1580
1581
1582
1583
1584 @JsxSetter({CHROME, EDGE})
1585 public void setOnbeforecut(final Object onbeforecut) {
1586 setEventHandler(Event.TYPE_BEFORECUT, onbeforecut);
1587 }
1588
1589
1590
1591
1592
1593
1594 @JsxGetter({CHROME, EDGE})
1595 public Function getOnbeforepaste() {
1596 return getEventHandler(Event.TYPE_BEFOREPASTE);
1597 }
1598
1599
1600
1601
1602
1603
1604 @JsxSetter({CHROME, EDGE})
1605 public void setOnbeforepaste(final Object onbeforepaste) {
1606 setEventHandler(Event.TYPE_BEFOREPASTE, onbeforepaste);
1607 }
1608
1609
1610
1611
1612
1613
1614 @JsxGetter({CHROME, EDGE})
1615 public Function getOnsearch() {
1616 return getEventHandler(Event.TYPE_SEARCH);
1617 }
1618
1619
1620
1621
1622
1623
1624 @JsxSetter({CHROME, EDGE})
1625 public void setOnsearch(final Object onsearch) {
1626 setEventHandler(Event.TYPE_SEARCH, onsearch);
1627 }
1628
1629
1630
1631
1632
1633
1634 @JsxGetter({CHROME, EDGE})
1635 public Function getOnwebkitfullscreenchange() {
1636 return getEventHandler(Event.TYPE_WEBKITFULLSCREENCHANGE);
1637 }
1638
1639
1640
1641
1642
1643
1644 @JsxSetter({CHROME, EDGE})
1645 public void setOnwebkitfullscreenchange(final Object onwebkitfullscreenchange) {
1646 setEventHandler(Event.TYPE_WEBKITFULLSCREENCHANGE, onwebkitfullscreenchange);
1647 }
1648
1649
1650
1651
1652
1653
1654 @JsxGetter({CHROME, EDGE})
1655 public Function getOnwebkitfullscreenerror() {
1656 return getEventHandler(Event.TYPE_WEBKITFULLSCREENERROR);
1657 }
1658
1659
1660
1661
1662
1663
1664 @JsxSetter({CHROME, EDGE})
1665 public void setOnwebkitfullscreenerror(final Object onwebkitfullscreenerror) {
1666 setEventHandler(Event.TYPE_WEBKITFULLSCREENERROR, onwebkitfullscreenerror);
1667 }
1668
1669
1670
1671
1672
1673
1674 public Function getOnwheel() {
1675 return getEventHandler(Event.TYPE_WHEEL);
1676 }
1677
1678
1679
1680
1681
1682
1683 public void setOnwheel(final Object onwheel) {
1684 setEventHandler(Event.TYPE_WHEEL, onwheel);
1685 }
1686
1687
1688
1689
1690 @Override
1691 @JsxFunction
1692 public void remove() {
1693 super.remove();
1694 }
1695
1696
1697
1698
1699
1700
1701
1702
1703 @JsxFunction({FF, FF_ESR})
1704 public void setCapture(final boolean retargetToElement) {
1705
1706 }
1707
1708
1709
1710
1711
1712 @JsxFunction({FF, FF_ESR})
1713 public void releaseCapture() {
1714
1715 }
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727 @JsxFunction
1728 public static void before(final Context context, final VarScope scope,
1729 final Scriptable thisObj, final Object[] args, final Function function) {
1730 Node.before(context, thisObj, args, function);
1731 }
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743 @JsxFunction
1744 public static void after(final Context context, final VarScope scope,
1745 final Scriptable thisObj, final Object[] args, final Function function) {
1746 Node.after(context, thisObj, args, function);
1747 }
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758 @JsxFunction
1759 public static void replaceWith(final Context context, final VarScope scope,
1760 final Scriptable thisObj, final Object[] args, final Function function) {
1761 Node.replaceWith(context, thisObj, args, function);
1762 }
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774 @JsxFunction
1775 public static boolean matches(final Context context, final VarScope scope,
1776 final Scriptable thisObj, final Object[] args, final Function function) {
1777 if (!(thisObj instanceof Element thisElement)) {
1778 throw JavaScriptEngine.typeError("Illegal invocation");
1779 }
1780
1781 final String selectorString = (String) args[0];
1782 try {
1783 final DomNode domNode = thisElement.getDomNodeOrNull();
1784 return domNode != null && ((DomElement) domNode).matches(selectorString, domNode);
1785 }
1786 catch (final CSSException e) {
1787 throw JavaScriptEngine.asJavaScriptException(
1788 (HtmlUnitScriptable) getTopLevelScope(scope).getGlobalThis(),
1789 "An invalid or illegal selector was specified (selector: '"
1790 + selectorString + "' error: " + e.getMessage() + ").",
1791 DOMException.SYNTAX_ERR);
1792 }
1793 }
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806 @JsxFunction({FF, FF_ESR})
1807 public static boolean mozMatchesSelector(final Context context, final VarScope scope,
1808 final Scriptable thisObj, final Object[] args, final Function function) {
1809 return matches(context, scope, thisObj, args, function);
1810 }
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823 @JsxFunction
1824 public static boolean webkitMatchesSelector(final Context context, final VarScope scope,
1825 final Scriptable thisObj, final Object[] args, final Function function) {
1826 return matches(context, scope, thisObj, args, function);
1827 }
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839 @JsxFunction
1840 public static Element closest(final Context context, final VarScope scope,
1841 final Scriptable thisObj, final Object[] args, final Function function) {
1842 if (!(thisObj instanceof Element)) {
1843 throw JavaScriptEngine.typeError("Illegal invocation");
1844 }
1845
1846 final String selectorString = (String) args[0];
1847 try {
1848 final DomNode domNode = ((Element) thisObj).getDomNodeOrNull();
1849 if (domNode == null) {
1850 return null;
1851 }
1852 final DomElement elem = domNode.closest(selectorString);
1853 if (elem == null) {
1854 return null;
1855 }
1856 return elem.getScriptableObject();
1857 }
1858 catch (final CSSException e) {
1859 throw JavaScriptEngine.syntaxError(
1860 "An invalid or illegal selector was specified (selector: '"
1861 + selectorString + "' error: " + e.getMessage() + ").");
1862 }
1863 }
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875 @JsxFunction
1876 public boolean toggleAttribute(final String name, final Object force) {
1877 if (JavaScriptEngine.isUndefined(force)) {
1878 if (hasAttribute(name)) {
1879 removeAttribute(name);
1880 return false;
1881 }
1882 setAttribute(name, "");
1883 return true;
1884 }
1885 if (JavaScriptEngine.toBoolean(force)) {
1886 setAttribute(name, "");
1887 return true;
1888 }
1889 removeAttribute(name);
1890 return false;
1891 }
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903 @JsxFunction
1904 public static void append(final Context context, final VarScope scope,
1905 final Scriptable thisObj, final Object[] args, final Function function) {
1906 if (!(thisObj instanceof Element)) {
1907 throw JavaScriptEngine.typeError("Illegal invocation");
1908 }
1909
1910 Node.append(context, thisObj, args, function);
1911 }
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923 @JsxFunction
1924 public static void prepend(final Context context, final VarScope scope,
1925 final Scriptable thisObj, final Object[] args, final Function function) {
1926 if (!(thisObj instanceof Element)) {
1927 throw JavaScriptEngine.typeError("Illegal invocation");
1928 }
1929
1930 Node.prepend(context, thisObj, args, function);
1931 }
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943 @JsxFunction
1944 public static void replaceChildren(final Context context, final VarScope scope,
1945 final Scriptable thisObj, final Object[] args, final Function function) {
1946 if (!(thisObj instanceof Element)) {
1947 throw JavaScriptEngine.typeError("Illegal invocation");
1948 }
1949
1950 Node.replaceChildren(context, thisObj, args, function);
1951 }
1952 }