1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host.html;
16
17 import static org.htmlunit.BrowserVersionFeatures.JS_FORM_DISPATCHEVENT_SUBMITS;
18
19 import java.io.Serializable;
20 import java.net.MalformedURLException;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.function.Supplier;
24
25 import org.htmlunit.FormEncodingType;
26 import org.htmlunit.WebAssert;
27 import org.htmlunit.corejs.javascript.Context;
28 import org.htmlunit.corejs.javascript.Function;
29 import org.htmlunit.corejs.javascript.Scriptable;
30 import org.htmlunit.corejs.javascript.ScriptableObject;
31 import org.htmlunit.corejs.javascript.VarScope;
32 import org.htmlunit.html.DomElement;
33 import org.htmlunit.html.DomNode;
34 import org.htmlunit.html.HtmlAttributeChangeEvent;
35 import org.htmlunit.html.HtmlElement;
36 import org.htmlunit.html.HtmlForm;
37 import org.htmlunit.html.HtmlImage;
38 import org.htmlunit.html.HtmlPage;
39 import org.htmlunit.html.SubmittableElement;
40 import org.htmlunit.javascript.JavaScriptEngine;
41 import org.htmlunit.javascript.configuration.JsxClass;
42 import org.htmlunit.javascript.configuration.JsxConstructor;
43 import org.htmlunit.javascript.configuration.JsxFunction;
44 import org.htmlunit.javascript.configuration.JsxGetter;
45 import org.htmlunit.javascript.configuration.JsxSetter;
46 import org.htmlunit.javascript.configuration.JsxSymbol;
47 import org.htmlunit.javascript.host.dom.AbstractList.EffectOnCache;
48 import org.htmlunit.javascript.host.dom.DOMTokenList;
49 import org.htmlunit.javascript.host.dom.RadioNodeList;
50 import org.htmlunit.javascript.host.event.Event;
51 import org.htmlunit.util.MimeType;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 @JsxClass(domClass = HtmlForm.class)
70 public class HTMLFormElement extends HTMLElement implements Function {
71
72
73
74
75 @Override
76 @JsxConstructor
77 public void jsConstructor() {
78 super.jsConstructor();
79 }
80
81
82
83
84
85 @JsxGetter
86 @Override
87 public String getName() {
88 return getHtmlForm().getNameAttribute();
89 }
90
91
92
93
94
95 @JsxSetter
96 @Override
97 public void setName(final String name) {
98 getHtmlForm().setNameAttribute(name);
99 }
100
101
102
103
104
105 @JsxGetter
106 public HTMLFormControlsCollection getElements() {
107 final HtmlForm htmlForm = getHtmlForm();
108
109 final HTMLFormControlsCollection elements = new HTMLFormControlsCollection(htmlForm, false) {
110 @Override
111 protected Object getWithPreemption(final String name) {
112 final List<HtmlElement> elementsForName = findElements(name);
113 if (elementsForName.isEmpty()) {
114 return NOT_FOUND;
115 }
116 if (elementsForName.size() == 1) {
117 return getScriptableFor(elementsForName.get(0));
118 }
119
120 final List<DomNode> nodes = new ArrayList<>(elementsForName);
121 final RadioNodeList nodeList = new RadioNodeList(getHtmlForm(), nodes);
122 nodeList.setElementsSupplier(
123 (Supplier<List<DomNode>> & Serializable) () -> new ArrayList<>(findElements(name)));
124 return nodeList;
125 }
126 };
127
128 elements.setElementsSupplier(
129 (Supplier<List<DomNode>> & Serializable)
130 () -> {
131 final DomNode domNode = getDomNodeOrNull();
132 if (domNode == null) {
133 return new ArrayList<>();
134 }
135 return new ArrayList<>(((HtmlForm) domNode).getElementsJS());
136 });
137
138 elements.setEffectOnCacheFunction(
139 (java.util.function.Function<HtmlAttributeChangeEvent, EffectOnCache> & Serializable)
140 event -> EffectOnCache.NONE);
141
142 return elements;
143 }
144
145
146
147
148 @JsxSymbol
149 public Scriptable iterator() {
150 return getElements().iterator();
151 }
152
153
154
155
156
157
158
159 @JsxGetter
160 public int getLength() {
161 return getElements().getLength();
162 }
163
164
165
166
167
168 @JsxGetter
169 public String getAction() {
170 final String action = getHtmlForm().getActionAttribute();
171
172 try {
173 return ((HtmlPage) getHtmlForm().getPage()).getFullyQualifiedUrl(action).toExternalForm();
174 }
175 catch (final MalformedURLException ignored) {
176
177 }
178 return action;
179 }
180
181
182
183
184
185 @JsxSetter
186 public void setAction(final String action) {
187 WebAssert.notNull("action", action);
188 getHtmlForm().setActionAttribute(action);
189 }
190
191
192
193
194
195 @JsxGetter
196 public String getMethod() {
197 return getHtmlForm().getMethodAttribute();
198 }
199
200
201
202
203
204 @JsxSetter
205 public void setMethod(final String method) {
206 WebAssert.notNull("method", method);
207 getHtmlForm().setMethodAttribute(method);
208 }
209
210
211
212
213
214 @JsxGetter
215 public String getTarget() {
216 return getHtmlForm().getTargetAttribute();
217 }
218
219
220
221
222
223 @JsxSetter
224 public void setTarget(final String target) {
225 WebAssert.notNull("target", target);
226 getHtmlForm().setTargetAttribute(target);
227 }
228
229
230
231
232
233 @JsxGetter
234 public String getRel() {
235 return getHtmlForm().getRelAttribute();
236 }
237
238
239
240
241
242 @JsxSetter
243 public void setRel(final String rel) {
244 getHtmlForm().setAttribute("rel", rel);
245 }
246
247
248
249
250
251 @JsxGetter
252 public DOMTokenList getRelList() {
253 return new DOMTokenList(this, "rel");
254 }
255
256
257
258
259
260 @JsxSetter
261 public void setRelList(final Object rel) {
262 if (JavaScriptEngine.isUndefined(rel)) {
263 setRel("undefined");
264 return;
265 }
266 setRel(JavaScriptEngine.toString(rel));
267 }
268
269
270
271
272
273 @JsxGetter
274 public String getEnctype() {
275 final String encoding = getHtmlForm().getEnctypeAttribute();
276 if (!FormEncodingType.URL_ENCODED.getName().equals(encoding)
277 && !FormEncodingType.MULTIPART.getName().equals(encoding)
278 && !MimeType.TEXT_PLAIN.equals(encoding)) {
279 return FormEncodingType.URL_ENCODED.getName();
280 }
281 return encoding;
282 }
283
284
285
286
287
288 @JsxSetter
289 public void setEnctype(final String enctype) {
290 WebAssert.notNull("encoding", enctype);
291 getHtmlForm().setEnctypeAttribute(enctype);
292 }
293
294
295
296
297
298 @JsxGetter
299 public String getEncoding() {
300 return getEnctype();
301 }
302
303
304
305
306
307 @JsxSetter
308 public void setEncoding(final String encoding) {
309 setEnctype(encoding);
310 }
311
312
313
314
315 public HtmlForm getHtmlForm() {
316 return (HtmlForm) getDomNodeOrDie();
317 }
318
319
320
321
322 @JsxFunction
323 public void submit() {
324 getHtmlForm().submit(null);
325 }
326
327
328
329
330
331
332
333
334 @JsxFunction
335 public void requestSubmit(final Object submitter) {
336 if (JavaScriptEngine.isUndefined(submitter)) {
337 submit();
338 return;
339 }
340
341 SubmittableElement submittable = null;
342 if (submitter instanceof HTMLElement subHtmlElement) {
343 if (subHtmlElement instanceof HTMLButtonElement element1) {
344 if ("submit".equals(element1.getType())) {
345 submittable = (SubmittableElement) subHtmlElement.getDomNodeOrDie();
346 }
347 }
348 else if (subHtmlElement instanceof HTMLInputElement element) {
349 if ("submit".equals(element.getType())) {
350 submittable = (SubmittableElement) subHtmlElement.getDomNodeOrDie();
351 }
352 }
353
354 if (submittable != null && subHtmlElement.getForm() != this) {
355 throw JavaScriptEngine.typeError(
356 "Failed to execute 'requestSubmit' on 'HTMLFormElement': "
357 + "The specified element is not owned by this form element.");
358 }
359 }
360
361 if (submittable == null) {
362 throw JavaScriptEngine.typeError(
363 "Failed to execute 'requestSubmit' on 'HTMLFormElement': "
364 + "The specified element is not a submit button.");
365 }
366
367 this.getHtmlForm().submit(submittable);
368 }
369
370
371
372
373 @JsxFunction
374 public void reset() {
375 getHtmlForm().reset();
376 }
377
378
379
380
381
382
383
384
385
386 @Override
387 protected Object getWithPreemption(final String name) {
388 if (getDomNodeOrNull() == null) {
389 return NOT_FOUND;
390 }
391 final List<HtmlElement> elements = findElements(name);
392
393 if (elements.isEmpty()) {
394 final HtmlElement element = getHtmlForm().getNamedElement(name);
395 return element != null ? getScriptableFor(element) : NOT_FOUND;
396 }
397 if (elements.size() == 1) {
398 final HtmlElement element = elements.get(0);
399 getHtmlForm().registerPastName(name, element);
400 return getScriptableFor(element);
401 }
402 final List<DomNode> nodes = new ArrayList<>(elements);
403
404 final RadioNodeList nodeList = new RadioNodeList(getHtmlForm(), nodes);
405 nodeList.setElementsSupplier(
406 (Supplier<List<DomNode>> & Serializable)
407 () -> new ArrayList<>(findElements(name)));
408 return nodeList;
409 }
410
411
412
413
414
415
416
417
418 @Override
419 public boolean has(final String name, final Scriptable start) {
420 if (super.has(name, start)) {
421 return true;
422 }
423
424 return findFirstElement(name) != null;
425 }
426
427
428
429
430
431
432
433
434 @Override
435 protected DescriptorInfo getOwnPropertyDescriptor(final Context cx, final Object id) {
436 final DescriptorInfo descInfo = super.getOwnPropertyDescriptor(cx, id);
437 if (descInfo != null) {
438 return descInfo;
439 }
440
441 if (id instanceof CharSequence) {
442 final HtmlElement element = findFirstElement(id.toString());
443 if (element != null) {
444 return ScriptableObject.buildDataDescriptor(element.getScriptableObject(),
445 ScriptableObject.READONLY | ScriptableObject.DONTENUM);
446 }
447 }
448
449 return null;
450 }
451
452 List<HtmlElement> findElements(final String name) {
453 final List<HtmlElement> elements = new ArrayList<>();
454 final HtmlForm form = (HtmlForm) getDomNodeOrNull();
455 if (form == null) {
456 return elements;
457 }
458
459 for (final HtmlElement element : form.getElementsJS()) {
460 if (name.equals(element.getId())
461 || name.equals(element.getAttributeDirect(DomElement.NAME_ATTRIBUTE))) {
462 elements.add(element);
463 }
464 }
465
466
467 if (elements.isEmpty()) {
468 for (final DomNode node : form.getHtmlElementDescendants()) {
469 if (node instanceof HtmlImage img) {
470 if (name.equals(img.getId()) || name.equals(img.getNameAttribute())) {
471 elements.add(img);
472 }
473 }
474 }
475 }
476
477 return elements;
478 }
479
480 private HtmlElement findFirstElement(final String name) {
481 final HtmlForm form = (HtmlForm) getDomNodeOrNull();
482 if (form == null) {
483 return null;
484 }
485
486 for (final HtmlElement node : form.getElementsJS()) {
487 if (name.equals(node.getId())
488 || name.equals(node.getAttributeDirect(DomElement.NAME_ATTRIBUTE))) {
489 return node;
490 }
491 }
492
493
494 for (final DomNode node : form.getHtmlElementDescendants()) {
495 if (node instanceof HtmlImage img) {
496 if (name.equals(img.getId()) || name.equals(img.getNameAttribute())) {
497 return img;
498 }
499 }
500 }
501
502 return null;
503 }
504
505
506
507
508
509
510
511 @Override
512 public Object get(final int index, final Scriptable start) {
513 if (getDomNodeOrNull() == null) {
514 return NOT_FOUND;
515 }
516 return getElements().get(index, ((HTMLFormElement) start).getElements());
517 }
518
519
520
521
522 @Override
523 public Object call(final Context cx, final VarScope scope, final Scriptable thisObj, final Object[] args) {
524 throw JavaScriptEngine.typeError("Not a function.");
525 }
526
527
528
529
530 @Override
531 public Scriptable construct(final Context cx, final VarScope scope, final Object[] args) {
532 throw JavaScriptEngine.typeError("Not a function.");
533 }
534
535 @Override
536 public boolean dispatchEvent(final Event event) {
537 final boolean result = super.dispatchEvent(event);
538
539 if (Event.TYPE_SUBMIT.equals(event.getType())
540 && getBrowserVersion().hasFeature(JS_FORM_DISPATCHEVENT_SUBMITS)) {
541 submit();
542 }
543 return result;
544 }
545
546
547
548
549
550 @JsxFunction
551 public boolean checkValidity() {
552 return getDomNodeOrDie().isValid();
553 }
554
555
556
557
558
559 @JsxGetter
560 public boolean isNoValidate() {
561 return getHtmlForm().isNoValidate();
562 }
563
564
565
566
567
568 @JsxSetter
569 public void setNoValidate(final boolean value) {
570 getHtmlForm().setNoValidate(value);
571 }
572 }