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
149 @JsxSymbol
150 public Scriptable iterator() {
151 return getElements().iterator();
152 }
153
154
155
156
157
158
159
160 @JsxGetter
161 public int getLength() {
162 return getElements().getLength();
163 }
164
165
166
167
168
169 @JsxGetter
170 public String getAction() {
171 final String action = getHtmlForm().getActionAttribute();
172
173 try {
174 return ((HtmlPage) getHtmlForm().getPage()).getFullyQualifiedUrl(action).toExternalForm();
175 }
176 catch (final MalformedURLException ignored) {
177
178 }
179 return action;
180 }
181
182
183
184
185
186 @JsxSetter
187 public void setAction(final String action) {
188 WebAssert.notNull("action", action);
189 getHtmlForm().setActionAttribute(action);
190 }
191
192
193
194
195
196 @JsxGetter
197 public String getMethod() {
198 return getHtmlForm().getMethodAttribute();
199 }
200
201
202
203
204
205 @JsxSetter
206 public void setMethod(final String method) {
207 WebAssert.notNull("method", method);
208 getHtmlForm().setMethodAttribute(method);
209 }
210
211
212
213
214
215 @JsxGetter
216 public String getTarget() {
217 return getHtmlForm().getTargetAttribute();
218 }
219
220
221
222
223
224 @JsxSetter
225 public void setTarget(final String target) {
226 WebAssert.notNull("target", target);
227 getHtmlForm().setTargetAttribute(target);
228 }
229
230
231
232
233
234 @JsxGetter
235 public String getRel() {
236 return getHtmlForm().getRelAttribute();
237 }
238
239
240
241
242
243 @JsxSetter
244 public void setRel(final String rel) {
245 getHtmlForm().setAttribute("rel", rel);
246 }
247
248
249
250
251
252 @JsxGetter
253 public DOMTokenList getRelList() {
254 return new DOMTokenList(this, "rel");
255 }
256
257
258
259
260
261 @JsxSetter
262 public void setRelList(final Object rel) {
263 if (JavaScriptEngine.isUndefined(rel)) {
264 setRel("undefined");
265 return;
266 }
267 setRel(JavaScriptEngine.toString(rel));
268 }
269
270
271
272
273
274 @JsxGetter
275 public String getEnctype() {
276 final String encoding = getHtmlForm().getEnctypeAttribute();
277 if (!FormEncodingType.URL_ENCODED.getName().equals(encoding)
278 && !FormEncodingType.MULTIPART.getName().equals(encoding)
279 && !MimeType.TEXT_PLAIN.equals(encoding)) {
280 return FormEncodingType.URL_ENCODED.getName();
281 }
282 return encoding;
283 }
284
285
286
287
288
289 @JsxSetter
290 public void setEnctype(final String enctype) {
291 WebAssert.notNull("encoding", enctype);
292 getHtmlForm().setEnctypeAttribute(enctype);
293 }
294
295
296
297
298
299 @JsxGetter
300 public String getEncoding() {
301 return getEnctype();
302 }
303
304
305
306
307
308 @JsxSetter
309 public void setEncoding(final String encoding) {
310 setEnctype(encoding);
311 }
312
313
314
315
316
317 public HtmlForm getHtmlForm() {
318 return (HtmlForm) getDomNodeOrDie();
319 }
320
321
322
323
324 @JsxFunction
325 public void submit() {
326 getHtmlForm().submit(null);
327 }
328
329
330
331
332
333
334
335
336 @JsxFunction
337 public void requestSubmit(final Object submitter) {
338 if (JavaScriptEngine.isUndefined(submitter)) {
339 submit();
340 return;
341 }
342
343 SubmittableElement submittable = null;
344 if (submitter instanceof HTMLElement subHtmlElement) {
345 if (subHtmlElement instanceof HTMLButtonElement element1) {
346 if ("submit".equals(element1.getType())) {
347 submittable = (SubmittableElement) subHtmlElement.getDomNodeOrDie();
348 }
349 }
350 else if (subHtmlElement instanceof HTMLInputElement element) {
351 if ("submit".equals(element.getType())) {
352 submittable = (SubmittableElement) subHtmlElement.getDomNodeOrDie();
353 }
354 }
355
356 if (submittable != null && subHtmlElement.getForm() != this) {
357 throw JavaScriptEngine.typeError(
358 "Failed to execute 'requestSubmit' on 'HTMLFormElement': "
359 + "The specified element is not owned by this form element.");
360 }
361 }
362
363 if (submittable == null) {
364 throw JavaScriptEngine.typeError(
365 "Failed to execute 'requestSubmit' on 'HTMLFormElement': "
366 + "The specified element is not a submit button.");
367 }
368
369 this.getHtmlForm().submit(submittable);
370 }
371
372
373
374
375 @JsxFunction
376 public void reset() {
377 getHtmlForm().reset();
378 }
379
380
381
382
383
384
385
386
387
388 @Override
389 protected Object getWithPreemption(final String name) {
390 if (getDomNodeOrNull() == null) {
391 return NOT_FOUND;
392 }
393 final List<HtmlElement> elements = findElements(name);
394
395 if (elements.isEmpty()) {
396 final HtmlElement element = getHtmlForm().getNamedElement(name);
397 return element != null ? getScriptableFor(element) : NOT_FOUND;
398 }
399 if (elements.size() == 1) {
400 final HtmlElement element = elements.get(0);
401 getHtmlForm().registerPastName(name, element);
402 return getScriptableFor(element);
403 }
404 final List<DomNode> nodes = new ArrayList<>(elements);
405
406 final RadioNodeList nodeList = new RadioNodeList(getHtmlForm(), nodes);
407 nodeList.setElementsSupplier(
408 (Supplier<List<DomNode>> & Serializable)
409 () -> new ArrayList<>(findElements(name)));
410 return nodeList;
411 }
412
413
414
415
416
417
418
419
420 @Override
421 public boolean has(final String name, final Scriptable start) {
422 if (super.has(name, start)) {
423 return true;
424 }
425
426 return findFirstElement(name) != null;
427 }
428
429
430
431
432
433
434
435
436 @Override
437 protected DescriptorInfo getOwnPropertyDescriptor(final Context cx, final Object id) {
438 final DescriptorInfo descInfo = super.getOwnPropertyDescriptor(cx, id);
439 if (descInfo != null) {
440 return descInfo;
441 }
442
443 if (id instanceof CharSequence) {
444 final HtmlElement element = findFirstElement(id.toString());
445 if (element != null) {
446 return ScriptableObject.buildDataDescriptor(element.getScriptableObject(),
447 ScriptableObject.READONLY | ScriptableObject.DONTENUM);
448 }
449 }
450
451 return null;
452 }
453
454 List<HtmlElement> findElements(final String name) {
455 final List<HtmlElement> elements = new ArrayList<>();
456 final HtmlForm form = (HtmlForm) getDomNodeOrNull();
457 if (form == null) {
458 return elements;
459 }
460
461 for (final HtmlElement element : form.getElementsJS()) {
462 if (name.equals(element.getId())
463 || name.equals(element.getAttributeDirect(DomElement.NAME_ATTRIBUTE))) {
464 elements.add(element);
465 }
466 }
467
468
469 if (elements.isEmpty()) {
470 for (final DomNode node : form.getHtmlElementDescendants()) {
471 if (node instanceof HtmlImage img) {
472 if (name.equals(img.getId()) || name.equals(img.getNameAttribute())) {
473 elements.add(img);
474 }
475 }
476 }
477 }
478
479 return elements;
480 }
481
482 private HtmlElement findFirstElement(final String name) {
483 final HtmlForm form = (HtmlForm) getDomNodeOrNull();
484 if (form == null) {
485 return null;
486 }
487
488 for (final HtmlElement node : form.getElementsJS()) {
489 if (name.equals(node.getId())
490 || name.equals(node.getAttributeDirect(DomElement.NAME_ATTRIBUTE))) {
491 return node;
492 }
493 }
494
495
496 for (final DomNode node : form.getHtmlElementDescendants()) {
497 if (node instanceof HtmlImage img) {
498 if (name.equals(img.getId()) || name.equals(img.getNameAttribute())) {
499 return img;
500 }
501 }
502 }
503
504 return null;
505 }
506
507
508
509
510
511
512
513 @Override
514 public Object get(final int index, final Scriptable start) {
515 if (getDomNodeOrNull() == null) {
516 return NOT_FOUND;
517 }
518 return getElements().get(index, ((HTMLFormElement) start).getElements());
519 }
520
521
522
523
524 @Override
525 public Object call(final Context cx, final VarScope scope, final Scriptable thisObj, final Object[] args) {
526 throw JavaScriptEngine.typeError("Not a function.");
527 }
528
529
530
531
532 @Override
533 public Scriptable construct(final Context cx, final VarScope scope, final Object[] args) {
534 throw JavaScriptEngine.typeError("Not a function.");
535 }
536
537 @Override
538 public boolean dispatchEvent(final Event event) {
539 final boolean result = super.dispatchEvent(event);
540
541 if (Event.TYPE_SUBMIT.equals(event.getType())
542 && getBrowserVersion().hasFeature(JS_FORM_DISPATCHEVENT_SUBMITS)) {
543 submit();
544 }
545 return result;
546 }
547
548
549
550
551
552 @JsxFunction
553 public boolean checkValidity() {
554 return getDomNodeOrDie().isValid();
555 }
556
557
558
559
560
561 @JsxGetter
562 public boolean isNoValidate() {
563 return getHtmlForm().isNoValidate();
564 }
565
566
567
568
569
570 @JsxSetter
571 public void setNoValidate(final boolean value) {
572 getHtmlForm().setNoValidate(value);
573 }
574 }