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.html;
16  
17  import static org.htmlunit.BrowserVersionFeatures.JS_INPUT_NUMBER_DOT_AT_END_IS_DOUBLE;
18  import static org.htmlunit.javascript.configuration.SupportedBrowser.FF;
19  import static org.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
20  
21  import java.io.IOException;
22  
23  import org.htmlunit.html.DomElement;
24  import org.htmlunit.html.DomNode;
25  import org.htmlunit.html.HtmlCheckBoxInput;
26  import org.htmlunit.html.HtmlFileInput;
27  import org.htmlunit.html.HtmlInput;
28  import org.htmlunit.html.HtmlNumberInput;
29  import org.htmlunit.html.HtmlRadioButtonInput;
30  import org.htmlunit.html.HtmlTextInput;
31  import org.htmlunit.html.impl.SelectableTextInput;
32  import org.htmlunit.javascript.JavaScriptEngine;
33  import org.htmlunit.javascript.configuration.JsxClass;
34  import org.htmlunit.javascript.configuration.JsxConstructor;
35  import org.htmlunit.javascript.configuration.JsxFunction;
36  import org.htmlunit.javascript.configuration.JsxGetter;
37  import org.htmlunit.javascript.configuration.JsxSetter;
38  import org.htmlunit.javascript.host.DOMRectList;
39  import org.htmlunit.javascript.host.Window;
40  import org.htmlunit.javascript.host.dom.DOMException;
41  import org.htmlunit.javascript.host.dom.NodeList;
42  import org.htmlunit.javascript.host.event.Event;
43  import org.htmlunit.javascript.host.file.FileList;
44  import org.htmlunit.util.StringUtils;
45  
46  /**
47   * The JavaScript object for {@link HtmlInput}.
48   *
49   * @author Mike Bowler
50   * @author Christian Sell
51   * @author Marc Guillemot
52   * @author Chris Erskine
53   * @author Ahmed Ashour
54   * @author Daniel Gredler
55   * @author Ronald Brill
56   * @author Frank Danek
57   * @author Anton Demydenko
58   */
59  @JsxClass(domClass = HtmlInput.class)
60  public class HTMLInputElement extends HTMLElement {
61  
62      /** "Live" labels collection; has to be a member to have equality (==) working. */
63      private NodeList labels_;
64  
65      /**
66       * JavaScript constructor.
67       */
68      @Override
69      @JsxConstructor
70      public void jsConstructor() {
71          super.jsConstructor();
72      }
73  
74      /**
75       * Returns the {@code type} property.
76       * @return the {@code type} property
77       */
78      @JsxGetter
79      public String getType() {
80          return getDomNodeOrDie().getType();
81      }
82  
83      /**
84       * Sets the value of the attribute {@code type}.
85       * Note: this replaces the DOM node with a new one.
86       * @param newType the new type to set
87       */
88      @JsxSetter
89      public void setType(final String newType) {
90          getDomNodeOrDie().changeType(newType, false);
91      }
92  
93      /**
94       * Sets the value of the JavaScript attribute {@code value}.
95       *
96       * @param newValue the new value
97       */
98      @JsxSetter
99      @Override
100     public void setValue(final Object newValue) {
101         if (null == newValue) {
102             getDomNodeOrDie().setValue("");
103             getDomNodeOrDie().valueModifiedByJavascript();
104             return;
105         }
106 
107         final String val = JavaScriptEngine.toString(newValue);
108         if ("file".equals(getType())) {
109             if (!StringUtils.isEmptyOrNull(val)) {
110                 throw JavaScriptEngine.asJavaScriptException(
111                         getWindow(),
112                         "Failed to set the 'value' property on 'HTMLInputElement'.",
113                         DOMException.INVALID_STATE_ERR);
114             }
115             return;
116         }
117 
118         getDomNodeOrDie().setValue(val);
119         getDomNodeOrDie().valueModifiedByJavascript();
120     }
121 
122     /**
123      * Sets the checked property. Although this property is defined in Input it
124      * doesn't make any sense for input's other than checkbox and radio. This
125      * implementation does nothing. The implementations in Checkbox and Radio
126      * actually do the work.
127      *
128      * @param checked True if this input should have the {@code checked} attribute set
129      */
130     @JsxSetter
131     public void setChecked(final boolean checked) {
132         getDomNodeOrDie().setChecked(checked);
133     }
134 
135     /**
136      * {@inheritDoc}
137      */
138     @Override
139     public HtmlInput getDomNodeOrDie() {
140         return (HtmlInput) super.getDomNodeOrDie();
141     }
142 
143     /**
144      * Returns the value of the checked property. Although this property is
145      * defined in Input it doesn't make any sense for input's other than
146      * checkbox and radio. This implementation does nothing. The
147      * implementations in Checkbox and Radio actually do the work.
148      *
149      * @return the checked property
150      */
151     @JsxGetter
152     public boolean isChecked() {
153         return getDomNodeOrDie().isChecked();
154     }
155 
156     /**
157      * Select this element.
158      */
159     @JsxFunction
160     public void select() {
161         final HtmlInput input = getDomNodeOrDie();
162         if (input instanceof HtmlTextInput) {
163             ((HtmlTextInput) input).select();
164         }
165         // currently nothing for other input types
166     }
167 
168     /**
169      * Returns the input's default value, used if the containing form gets reset.
170      * @return the input's default value, used if the containing form gets reset
171      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533718.aspx">MSDN Documentation</a>
172      */
173     @JsxGetter
174     public String getDefaultValue() {
175         return getDomNodeOrDie().getDefaultValue();
176     }
177 
178     /**
179      * Sets the input's default value, used if the containing form gets reset.
180      * @param defaultValue the input's default value, used if the containing form gets reset
181      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533718.aspx">MSDN Documentation</a>
182      */
183     @JsxSetter
184     public void setDefaultValue(final String defaultValue) {
185         getDomNodeOrDie().setDefaultValue(defaultValue);
186     }
187 
188     /**
189      * Returns the input's default checked value, used if the containing form gets reset.
190      * @return the input's default checked value, used if the containing form gets reset
191      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533715.aspx">MSDN Documentation</a>
192      */
193     @JsxGetter
194     public boolean isDefaultChecked() {
195         return getDomNodeOrDie().isDefaultChecked();
196     }
197 
198     /**
199      * Sets the input's default checked value, used if the containing form gets reset.
200      * @param defaultChecked the input's default checked value, used if the containing form gets reset
201      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533715.aspx">MSDN Documentation</a>
202      */
203     @JsxSetter
204     public void setDefaultChecked(final boolean defaultChecked) {
205         getDomNodeOrDie().setDefaultChecked(defaultChecked);
206     }
207 
208     /**
209      * Gets the value of {@code textLength} attribute.
210      * @return the text length
211      */
212     @JsxGetter({FF, FF_ESR})
213     public int getTextLength() {
214         return getValue().length();
215     }
216 
217     /**
218      * Gets the value of {@code selectionStart} attribute.
219      * @return the selection start
220      */
221     @JsxGetter
222     public Integer getSelectionStart() {
223         final DomNode dom = getDomNodeOrDie();
224         if (dom instanceof SelectableTextInput) {
225             if ("number".equals(getType())) {
226                 return null;
227             }
228 
229             return ((SelectableTextInput) dom).getSelectionStart();
230         }
231 
232         return null;
233     }
234 
235     /**
236      * Sets the value of {@code selectionStart} attribute.
237      * @param start selection start
238      */
239     @JsxSetter
240     public void setSelectionStart(final int start) {
241         final DomNode dom = getDomNodeOrDie();
242         if (dom instanceof SelectableTextInput) {
243             if ("number".equals(getType())) {
244                 throw JavaScriptEngine.asJavaScriptException(
245                         getWindow(),
246                         "Failed to set the 'selectionStart' property"
247                                 + "from 'HTMLInputElement': "
248                                 + "The input element's type ('number') does not support selection.",
249                         DOMException.INVALID_STATE_ERR);
250             }
251 
252             ((SelectableTextInput) dom).setSelectionStart(start);
253             return;
254         }
255 
256         throw JavaScriptEngine.asJavaScriptException(
257                 getWindow(),
258                 "Failed to set the 'selectionStart' property from 'HTMLInputElement': "
259                         + "The input element's type (" + getType() + ") does not support selection.",
260                 DOMException.INVALID_STATE_ERR);
261     }
262 
263     /**
264      * Gets the value of {@code selectionEnd} attribute.
265      * @return the selection end
266      */
267     @JsxGetter
268     public Integer getSelectionEnd() {
269         final DomNode dom = getDomNodeOrDie();
270         if (dom instanceof SelectableTextInput) {
271             if ("number".equals(getType())) {
272                 return null;
273             }
274 
275             return ((SelectableTextInput) dom).getSelectionEnd();
276         }
277 
278         return null;
279     }
280 
281     /**
282      * Sets the value of {@code selectionEnd} attribute.
283      * @param end selection end
284      */
285     @JsxSetter
286     public void setSelectionEnd(final int end) {
287         final DomNode dom = getDomNodeOrDie();
288         if (dom instanceof SelectableTextInput) {
289             if ("number".equals(getType())) {
290                 throw JavaScriptEngine.asJavaScriptException(
291                         getWindow(),
292                         "Failed to set the 'selectionEnd' property"
293                                 + "from 'HTMLInputElement': "
294                                 + "The input element's type ('number') does not support selection.",
295                         DOMException.INVALID_STATE_ERR);
296             }
297 
298             ((SelectableTextInput) dom).setSelectionEnd(end);
299             return;
300         }
301 
302         throw JavaScriptEngine.asJavaScriptException(
303                 getWindow(),
304                 "Failed to set the 'selectionEnd' property from 'HTMLInputElement': "
305                         + "The input element's type (" + getType() + ") does not support selection.",
306                 DOMException.INVALID_STATE_ERR);
307     }
308 
309     /**
310      * Gets the max length.
311      * @return the max length
312      */
313     @JsxGetter
314     public int getMaxLength() {
315         final String attrValue = getDomNodeOrDie().getAttribute("maxLength");
316         return StringUtils.toInt(attrValue, -1);
317     }
318 
319     /**
320      * Sets the value of {@code maxLength} attribute.
321      * @param length the new value
322      */
323     @JsxSetter
324     public void setMaxLength(final int length) {
325         getDomNodeOrDie().setMaxLength(length);
326     }
327 
328     /**
329      * Gets the {@code minLength}.
330      * @return the {@code minLength}
331      */
332     @JsxGetter
333     public int getMinLength() {
334         final String attrValue = getDomNodeOrDie().getAttribute("minLength");
335         return StringUtils.toInt(attrValue, -1);
336     }
337 
338     /**
339      * Sets the value of {@code minLength} attribute.
340      * @param length the new value
341      */
342     @JsxSetter
343     public void setMinLength(final int length) {
344         getDomNodeOrDie().setMinLength(length);
345     }
346 
347     /**
348      * Gets the {@code min} property.
349      * @return the {@code min} property
350      */
351     @JsxGetter
352     public String getMin() {
353         return getDomNodeOrDie().getAttributeDirect("min");
354     }
355 
356     /**
357      * Sets the {@code min} property.
358      * @param min the {@code min} property
359      */
360     @JsxSetter
361     public void setMin(final String min) {
362         getDomNodeOrDie().setAttribute("min", min);
363     }
364 
365     /**
366      * Gets the {@code max} property.
367      * @return the {@code max} property
368      */
369     @JsxGetter
370     public String getMax() {
371         return getDomNodeOrDie().getAttributeDirect("max");
372     }
373 
374     /**
375      * Sets the {@code max} property.
376      * @param max the {@code max} property
377      */
378     @JsxSetter
379     public void setMax(final String max) {
380         getDomNodeOrDie().setAttribute("max", max);
381     }
382 
383     /**
384      * Gets the {@code step} property.
385      * @return the {@code step} property
386      */
387     @JsxGetter
388     public String getStep() {
389         return getDomNodeOrDie().getAttributeDirect("step");
390     }
391 
392     /**
393      * Sets the {@code step} property.
394      * @param step the {@code step} property
395      */
396     @JsxSetter
397     public void setStep(final String step) {
398         getDomNodeOrDie().setAttribute("step", step);
399     }
400 
401     /**
402      * Gets the value of {@code readOnly} attribute.
403      * @return the readOnly attribute
404      */
405     @JsxGetter
406     public boolean isReadOnly() {
407         return getDomNodeOrDie().isReadOnly();
408     }
409 
410     /**
411      * Sets the value of {@code readOnly} attribute.
412      * @param readOnly the new value
413      */
414     @JsxSetter
415     public void setReadOnly(final boolean readOnly) {
416         getDomNodeOrDie().setReadOnly(readOnly);
417     }
418 
419     /**
420      * Sets the selected portion of this input element.
421      * @param start the index of the first character to select
422      * @param end the index of the character after the selection
423      */
424     @JsxFunction
425     public void setSelectionRange(final int start, final int end) {
426         setSelectionStart(start);
427         setSelectionEnd(end);
428     }
429 
430     /**
431      * Returns the value of the {@code alt} property.
432      * @return the value of the {@code alt} property
433      */
434     @JsxGetter
435     public String getAlt() {
436         return getDomNodeOrDie().getAttributeDirect("alt");
437     }
438 
439     /**
440      * Returns the value of the {@code alt} property.
441      * @param alt the value
442      */
443     @JsxSetter
444     public void setAlt(final String alt) {
445         getDomNodeOrDie().setAttribute("alt", alt);
446     }
447 
448     /**
449      * Returns the value of the {@code align} property.
450      * @return the value of the {@code align} property
451      */
452     @JsxGetter
453     public String getAlign() {
454         return getAlign(true);
455     }
456 
457     /**
458      * Sets the value of the {@code align} property.
459      * @param align the value of the {@code align} property
460      */
461     @JsxSetter
462     public void setAlign(final String align) {
463         setAlign(align, false);
464     }
465 
466     /**
467      * Returns the value of the {@code src} attribute.
468      * @return the value of the {@code src} attribute
469      */
470     @JsxGetter
471     public String getSrc() {
472         return getDomNodeOrDie().getSrc();
473     }
474 
475     /**
476      * Sets the value of the {@code src} attribute.
477      * @param src the new value
478      */
479     @JsxSetter
480     public void setSrc(final String src) {
481         getDomNodeOrDie().setSrcAttribute(src);
482     }
483 
484     /**
485      * Returns the value of the JavaScript attribute {@code value}.
486      *
487      * @return the value of this attribute
488      */
489     @JsxGetter
490     @Override
491     public String getValue() {
492         final HtmlInput htmlInput = getDomNodeOrDie();
493 
494         if (htmlInput instanceof HtmlNumberInput) {
495             final String valueAttr = htmlInput.getValue();
496             if (!valueAttr.isEmpty()) {
497                 if (org.htmlunit.util.StringUtils.equalsChar('-', valueAttr)
498                         || org.htmlunit.util.StringUtils.equalsChar('+', valueAttr)) {
499                     return "";
500                 }
501 
502                 final int lastPos = valueAttr.length() - 1;
503                 if (lastPos >= 0 && valueAttr.charAt(lastPos) == '.') {
504                     if (htmlInput.hasFeature(JS_INPUT_NUMBER_DOT_AT_END_IS_DOUBLE)) {
505                         return "";
506                     }
507                 }
508                 try {
509                     Double.parseDouble(valueAttr);
510                 }
511                 catch (final NumberFormatException e) {
512                     return "";
513                 }
514             }
515         }
516 
517         return htmlInput.getValue();
518     }
519 
520     /**
521      * {@inheritDoc}
522      */
523     @Override
524     public String getAttribute(final String attributeName) {
525         final String superAttribute = super.getAttribute(attributeName);
526         if (DomElement.VALUE_ATTRIBUTE.equalsIgnoreCase(attributeName)) {
527             if ((superAttribute == null || !superAttribute.isEmpty())
528                     && getDefaultValue().isEmpty()) {
529                 return null;
530             }
531             if (!"file".equals(getType())) {
532                 return getDefaultValue();
533             }
534         }
535         return superAttribute;
536     }
537 
538     /**
539      * {@inheritDoc}
540      */
541     @Override
542     public void click() throws IOException {
543         final HtmlInput domNode = getDomNodeOrDie();
544         final boolean originalState = domNode.isChecked();
545 
546         domNode.click(false, false, false, false, false, true, false);
547 
548         final boolean newState = domNode.isChecked();
549 
550         if (originalState != newState
551                 && (domNode instanceof HtmlRadioButtonInput || domNode instanceof HtmlCheckBoxInput)) {
552             domNode.fireEvent(Event.TYPE_CHANGE);
553         }
554     }
555 
556     /**
557      * {@inheritDoc}
558      */
559     @Override
560     protected boolean isEndTagForbidden() {
561         return true;
562     }
563 
564     /**
565      * Returns the {@code required} property.
566      * @return the {@code required} property
567      */
568     @JsxGetter
569     public boolean isRequired() {
570         return getDomNodeOrDie().isRequired();
571     }
572 
573     /**
574      * Sets the {@code required} property.
575      * @param required the new value
576      */
577     @JsxSetter
578     public void setRequired(final boolean required) {
579         getDomNodeOrDie().setRequired(required);
580     }
581 
582     /**
583      * Returns the {@code size} attribute.
584      * @return the {@code size} attribute
585      */
586     @JsxGetter
587     public String getSize() {
588         return getDomNodeOrDie().getSize();
589     }
590 
591     /**
592      * Sets the {@code size} attribute.
593      * @param size the new {@code size} value
594      */
595     @JsxSetter
596     public void setSize(final String size) {
597         getDomNodeOrDie().setSize(size);
598     }
599 
600     /**
601      * Returns the {@code accept} attribute.
602      * @return the {@code accept} attribute
603      */
604     @JsxGetter
605     public String getAccept() {
606         return getDomNodeOrDie().getAccept();
607     }
608 
609     /**
610      * Sets the {@code accept} attribute.
611      * @param accept the new {@code accept} value
612      */
613     @JsxSetter
614     public void setAccept(final String accept) {
615         getDomNodeOrDie().setAccept(accept);
616     }
617 
618     /**
619      * Returns the {@code autocomplete} attribute.
620      * @return the {@code autocomplete} attribute
621      */
622     @JsxGetter
623     public String getAutocomplete() {
624         return getDomNodeOrDie().getAutocomplete();
625     }
626 
627     /**
628      * Sets the {@code autocomplete} attribute.
629      * @param autocomplete the new {@code autocomplete} value
630      */
631     @JsxSetter
632     public void setAutocomplete(final String autocomplete) {
633         getDomNodeOrDie().setAutocomplete(autocomplete);
634     }
635 
636     /**
637      * Returns the {@code files} property.
638      * @return the {@code files} property
639      */
640     @JsxGetter
641     public FileList getFiles() {
642         final HtmlInput htmlInput = getDomNodeOrDie();
643         if (htmlInput instanceof HtmlFileInput) {
644             final FileList list = new FileList(((HtmlFileInput) htmlInput).getFiles());
645             list.setParentScope(getParentScope());
646             list.setPrototype(getPrototype(list.getClass()));
647             return list;
648         }
649         return null;
650     }
651 
652     /**
653      * Returns the {@code placeholder} attribute.
654      * @return the {@code placeholder} attribute
655      */
656     @JsxGetter
657     public String getPlaceholder() {
658         return getDomNodeOrDie().getPlaceholder();
659     }
660 
661     /**
662      * Sets the {@code placeholder} attribute.
663      * @param placeholder the new {@code placeholder} value
664      */
665     @JsxSetter
666     public void setPlaceholder(final String placeholder) {
667         getDomNodeOrDie().setPlaceholder(placeholder);
668     }
669 
670     /**
671      * Returns the {@code width} property.
672      * @return the {@code width} property
673      */
674     @JsxGetter
675     public int getWidth() {
676         final String value = getDomNodeOrDie().getAttributeDirect("width");
677         final Integer intValue = HTMLCanvasElement.getValue(value);
678         if (intValue != null) {
679             return intValue;
680         }
681         return 0;
682     }
683 
684     /**
685      * Sets the {@code width} property.
686      * @param width the {@code width} property
687      */
688     @JsxSetter
689     public void setWidth(final int width) {
690         getDomNodeOrDie().setAttribute("width", Integer.toString(width));
691     }
692 
693     /**
694      * Returns the {@code height} property.
695      * @return the {@code height} property
696      */
697     @JsxGetter
698     public int getHeight() {
699         final String value = getDomNodeOrDie().getAttributeDirect("height");
700         final Integer intValue = HTMLCanvasElement.getValue(value);
701         if (intValue != null) {
702             return intValue;
703         }
704         return 0;
705     }
706 
707     /**
708      * Sets the {@code height} property.
709      * @param height the {@code height} property
710      */
711     @JsxSetter
712     public void setHeight(final int height) {
713         getDomNodeOrDie().setAttribute("height", Integer.toString(height));
714     }
715 
716     /**
717      * Returns the labels associated with the element.
718      * @return the labels associated with the element
719      */
720     @JsxGetter
721     public NodeList getLabels() {
722         if (labels_ == null) {
723             labels_ = new LabelsNodeList(getDomNodeOrDie());
724         }
725         return labels_;
726     }
727 
728     /**
729      * Checks whether the element has any constraints and whether it satisfies them.
730      * @return if the element is valid
731      */
732     @JsxFunction
733     public boolean checkValidity() {
734         return getDomNodeOrDie().isValid();
735     }
736 
737     /**
738      * {@inheritDoc}
739      */
740     @JsxGetter
741     @Override
742     public String getName() {
743         return super.getName();
744     }
745 
746     /**
747      * {@inheritDoc}
748      */
749     @JsxSetter
750     @Override
751     public void setName(final String newName) {
752         super.setName(newName);
753     }
754 
755     /**
756      * {@inheritDoc} Overridden to modify browser configurations.
757      */
758     @Override
759     @JsxGetter
760     public boolean isDisabled() {
761         return super.isDisabled();
762     }
763 
764     /**
765      * {@inheritDoc} Overridden to modify browser configurations.
766      */
767     @Override
768     @JsxSetter
769     public void setDisabled(final boolean disabled) {
770         super.setDisabled(disabled);
771     }
772 
773     /**
774      * {@inheritDoc}
775      */
776     @JsxGetter
777     @Override
778     public HTMLFormElement getForm() {
779         return super.getForm();
780     }
781 
782     /**
783      * @return a ValidityState with the validity states that this element is in.
784      */
785     @JsxGetter
786     public ValidityState getValidity() {
787         final ValidityState validityState = new ValidityState();
788         validityState.setPrototype(getPrototype(validityState.getClass()));
789         validityState.setParentScope(getParentScope());
790         validityState.setDomNode(getDomNodeOrDie());
791         return validityState;
792     }
793 
794     /**
795      * @return whether the element is a candidate for constraint validation
796      */
797     @JsxGetter
798     public boolean isWillValidate() {
799         return getDomNodeOrDie().willValidate();
800     }
801 
802     /**
803      * Sets the custom validity message for the element to the specified message.
804      * @param message the new message
805      */
806     @JsxFunction
807     public void setCustomValidity(final String message) {
808         getDomNodeOrDie().setCustomValidity(message);
809     }
810 
811     /**
812      * Returns the value of the property {@code formnovalidate}.
813      * @return the value of this property
814      */
815     @JsxGetter
816     public boolean isFormNoValidate() {
817         return getDomNodeOrDie().isFormNoValidate();
818     }
819 
820     /**
821      * Sets the value of the property {@code formnovalidate}.
822      * @param value the new value
823      */
824     @JsxSetter
825     public void setFormNoValidate(final boolean value) {
826         getDomNodeOrDie().setFormNoValidate(value);
827     }
828 
829     /**
830      * {@inheritDoc}
831      */
832     @Override
833     public DOMRectList getClientRects() {
834         if ("hidden".equals(getType())) {
835             final Window w = getWindow();
836             final DOMRectList rectList = new DOMRectList();
837             rectList.setParentScope(w);
838             rectList.setPrototype(getPrototype(rectList.getClass()));
839 
840             return rectList;
841         }
842 
843         return super.getClientRects();
844     }
845 }