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