com.gargoylesoftware.htmlunit.javascript.host.html.HTMLFormElement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of htmlunit Show documentation
Show all versions of htmlunit Show documentation
A headless browser intended for use in testing web-based applications.
/*
* Copyright (c) 2002-2021 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gargoylesoftware.htmlunit.javascript.host.html;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.FORMFIELD_REACHABLE_BY_NEW_NAMES;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.FORMFIELD_REACHABLE_BY_ORIGINAL_NAME;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.FORM_SUBMISSION_DOWNLOWDS_ALSO_IF_ONLY_HASH_CHANGED;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_FORM_ACTION_EXPANDURL_NOT_DEFINED;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_FORM_DISPATCHEVENT_SUBMITS;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_FORM_REJECT_INVALID_ENCODING;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_FORM_SUBMIT_FORCES_DOWNLOAD;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_FORM_USABLE_AS_FUNCTION;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF78;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.gargoylesoftware.htmlunit.FormEncodingType;
import com.gargoylesoftware.htmlunit.WebAssert;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.FormFieldWithNameHistory;
import com.gargoylesoftware.htmlunit.html.HtmlAttributeChangeEvent;
import com.gargoylesoftware.htmlunit.html.HtmlButton;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlImage;
import com.gargoylesoftware.htmlunit.html.HtmlImageInput;
import com.gargoylesoftware.htmlunit.html.HtmlInput;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
import com.gargoylesoftware.htmlunit.html.SubmittableElement;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
import com.gargoylesoftware.htmlunit.protocol.javascript.JavaScriptURLConnection;
import com.gargoylesoftware.htmlunit.util.MimeType;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
/**
* A JavaScript object for {@code HTMLFormElement}.
*
* @author Mike Bowler
* @author Daniel Gredler
* @author Kent Tong
* @author Chris Erskine
* @author Marc Guillemot
* @author Ahmed Ashour
* @author Sudhan Moghe
* @author Ronald Brill
* @author Frank Danek
*
* @see MSDN documentation
*/
@JsxClass(domClass = HtmlForm.class)
public class HTMLFormElement extends HTMLElement implements Function {
/**
* Creates an instance.
*/
@JsxConstructor({CHROME, EDGE, FF, FF78})
public HTMLFormElement() {
}
/**
* {@inheritDoc}
*/
@Override
public void setHtmlElement(final HtmlElement htmlElement) {
super.setHtmlElement(htmlElement);
final HtmlForm htmlForm = getHtmlForm();
htmlForm.setScriptableObject(this);
}
/**
* Returns the value of the property {@code name}.
* @return the value of this property
*/
@JsxGetter
@Override
public String getName() {
return getHtmlForm().getNameAttribute();
}
/**
* Sets the value of the property {@code name}.
* @param name the new value
*/
@JsxSetter
@Override
public void setName(final String name) {
getHtmlForm().setNameAttribute(name);
}
/**
* Returns the value of the property {@code elements}.
* @return the value of this property
*/
@JsxGetter
public HTMLCollection getElements() {
final HtmlForm htmlForm = getHtmlForm();
return new HTMLCollection(htmlForm, false) {
private boolean filterChildrenOfNestedForms_;
@Override
protected List computeElements() {
final List response = super.computeElements();
// it would be more performant to avoid iterating through
// nested forms but as it is a corner case of ill formed HTML
// the needed refactoring would take too much time
// => filter here and not in isMatching as it won't be needed in most
// of the cases
if (filterChildrenOfNestedForms_) {
for (final Iterator iter = response.iterator(); iter.hasNext();) {
final HtmlElement field = (HtmlElement) iter.next();
if (field.getEnclosingForm() != htmlForm) {
iter.remove();
}
}
}
response.addAll(htmlForm.getLostChildren());
return response;
}
@Override
protected Object getWithPreemption(final String name) {
return HTMLFormElement.this.getWithPreemption(name);
}
@Override
public EffectOnCache getEffectOnCache(final HtmlAttributeChangeEvent event) {
return EffectOnCache.NONE;
}
@Override
protected boolean isMatching(final DomNode node) {
if (node instanceof HtmlForm) {
filterChildrenOfNestedForms_ = true;
return false;
}
return node instanceof HtmlInput || node instanceof HtmlButton
|| node instanceof HtmlTextArea || node instanceof HtmlSelect;
}
};
}
/**
* Returns the value of the property {@code length}.
* Does not count input {@code type=image} elements
* (MSDN doc)
* @return the value of this property
*/
@JsxGetter
public int getLength() {
final int all = getElements().getLength();
final int images = getHtmlForm().getElementsByAttribute(HtmlInput.TAG_NAME, "type", "image").size();
return all - images;
}
/**
* Returns the value of the property {@code action}.
* @return the value of this property
*/
@JsxGetter
public String getAction() {
final String action = getHtmlForm().getActionAttribute();
if (action == DomElement.ATTRIBUTE_NOT_DEFINED
&& !getBrowserVersion().hasFeature(JS_FORM_ACTION_EXPANDURL_NOT_DEFINED)) {
return action;
}
try {
return ((HtmlPage) getHtmlForm().getPage()).getFullyQualifiedUrl(action).toExternalForm();
}
catch (final MalformedURLException e) {
// nothing, return action attribute
}
return action;
}
/**
* Sets the value of the property {@code action}.
* @param action the new value
*/
@JsxSetter
public void setAction(final String action) {
WebAssert.notNull("action", action);
getHtmlForm().setActionAttribute(action);
}
/**
* Returns the value of the property {@code method}.
* @return the value of this property
*/
@JsxGetter
public String getMethod() {
return getHtmlForm().getMethodAttribute();
}
/**
* Sets the value of the property {@code method}.
* @param method the new property
*/
@JsxSetter
public void setMethod(final String method) {
WebAssert.notNull("method", method);
getHtmlForm().setMethodAttribute(method);
}
/**
* Returns the value of the property {@code target}.
* @return the value of this property
*/
@JsxGetter
public String getTarget() {
return getHtmlForm().getTargetAttribute();
}
/**
* Sets the value of the property {@code target}.
* @param target the new value
*/
@JsxSetter
public void setTarget(final String target) {
WebAssert.notNull("target", target);
getHtmlForm().setTargetAttribute(target);
}
/**
* Returns the value of the property {@code enctype}.
* @return the value of this property
*/
@JsxGetter
public String getEnctype() {
final String encoding = getHtmlForm().getEnctypeAttribute();
if (!FormEncodingType.URL_ENCODED.getName().equals(encoding)
&& !FormEncodingType.MULTIPART.getName().equals(encoding)
&& !MimeType.TEXT_PLAIN.equals(encoding)) {
return FormEncodingType.URL_ENCODED.getName();
}
return encoding;
}
/**
* Sets the value of the property {@code enctype}.
* @param enctype the new value
*/
@JsxSetter
public void setEnctype(final String enctype) {
WebAssert.notNull("encoding", enctype);
if (getBrowserVersion().hasFeature(JS_FORM_REJECT_INVALID_ENCODING)
&& !FormEncodingType.URL_ENCODED.getName().equals(enctype)
&& !FormEncodingType.MULTIPART.getName().equals(enctype)
&& !FormEncodingType.TEXT_PLAIN.getName().equals(enctype)) {
throw Context.reportRuntimeError("Cannot set the encoding property to invalid value: '" + enctype + "'");
}
getHtmlForm().setEnctypeAttribute(enctype);
}
/**
* Returns the value of the property {@code encoding}.
* @return the value of this property
*/
@JsxGetter
public String getEncoding() {
return getEnctype();
}
/**
* Sets the value of the property {@code encoding}.
* @param encoding the new value
*/
@JsxSetter
public void setEncoding(final String encoding) {
setEnctype(encoding);
}
/**
* @return the associated HtmlForm
*/
public HtmlForm getHtmlForm() {
return (HtmlForm) getDomNodeOrDie();
}
/**
* Submits the form (at the end of the current script execution).
*/
@JsxFunction
public void submit() {
final HtmlPage page = (HtmlPage) getDomNodeOrDie().getPage();
final WebClient webClient = page.getWebClient();
final String action = getHtmlForm().getActionAttribute().trim();
if (StringUtils.startsWithIgnoreCase(action, JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
final String js = action.substring(JavaScriptURLConnection.JAVASCRIPT_PREFIX.length());
webClient.getJavaScriptEngine().execute(page, js, "Form action", 0);
}
else {
// download should be done ASAP, response will be loaded into a window later
final WebRequest request = getHtmlForm().getWebRequest(null);
final String target = page.getResolvedTarget(getTarget());
final boolean forceDownload = webClient.getBrowserVersion().hasFeature(JS_FORM_SUBMIT_FORCES_DOWNLOAD);
final boolean checkHash =
!webClient.getBrowserVersion().hasFeature(FORM_SUBMISSION_DOWNLOWDS_ALSO_IF_ONLY_HASH_CHANGED);
webClient.download(page.getEnclosingWindow(),
target, request, checkHash, forceDownload, false, "JS form.submit()");
}
}
/**
* Submits the form by submitted using a specific submit button.
* @param submitter The submit button whose attributes describe the method
* by which the form is to be submitted. This may be either
* an <input> or <button> element whose type attribute is submit.
* If you omit the submitter parameter, the form element itself is used as the submitter.
*/
@JsxFunction({CHROME, EDGE, FF, FF78})
public void requestSubmit(final Object submitter) {
SubmittableElement submittable = null;
if (Undefined.isUndefined(submitter)) {
submit();
return;
}
if (submitter instanceof HTMLElement) {
final HTMLElement subHtmlElement = (HTMLElement) submitter;
if (subHtmlElement instanceof HTMLButtonElement) {
if ("submit".equals(((HTMLButtonElement) subHtmlElement).getType())) {
submittable = (SubmittableElement) subHtmlElement.getDomNodeOrDie();
}
}
else if (subHtmlElement instanceof HTMLInputElement) {
if ("submit".equals(((HTMLInputElement) subHtmlElement).getType())) {
submittable = (SubmittableElement) subHtmlElement.getDomNodeOrDie();
}
}
if (submittable != null && subHtmlElement.getForm() != this) {
throw ScriptRuntime.typeError(
"Failed to execute 'requestSubmit' on 'HTMLFormElement': "
+ "The specified element is not owned by this form element.");
}
}
if (submittable == null) {
throw ScriptRuntime.typeError(
"Failed to execute 'requestSubmit' on 'HTMLFormElement': "
+ "The specified element is not a submit button.");
}
this.getHtmlForm().submit(submittable);
}
/**
* Retrieves a form object or an object from an elements collection.
* @param index Integer or String that specifies the object or collection to retrieve.
* If this parameter is an integer, it is the zero-based index of the object.
* If this parameter is a string, all objects with matching name or id properties are retrieved,
* and a collection is returned if more than one match is made
* @param subIndex Optional. Integer that specifies the zero-based index of the object to retrieve
* when a collection is returned
* @return an object or a collection of objects if successful, or null otherwise
*/
@JsxFunction(IE)
public Object item(final Object index, final Object subIndex) {
if (index instanceof Number) {
return getElements().item(index);
}
final String name = Context.toString(index);
final Object response = getWithPreemption(name);
if (subIndex instanceof Number && response instanceof HTMLCollection) {
return ((HTMLCollection) response).item(subIndex);
}
return response;
}
/**
* Resets this form.
*/
@JsxFunction
public void reset() {
getHtmlForm().reset();
}
/**
* Overridden to allow the retrieval of certain form elements by ID or name.
*
* @param name {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
protected Object getWithPreemption(final String name) {
if (getDomNodeOrNull() == null) {
return NOT_FOUND;
}
final List elements = findElements(name);
if (elements.isEmpty()) {
return NOT_FOUND;
}
if (elements.size() == 1) {
return getScriptableFor(elements.get(0));
}
final List nodes = new ArrayList<>(elements);
return new HTMLCollection(getHtmlForm(), nodes) {
@Override
protected List computeElements() {
return new ArrayList<>(findElements(name));
}
};
}
List findElements(final String name) {
final List elements = new ArrayList<>();
addElements(name, getHtmlForm().getHtmlElementDescendants(), elements);
addElements(name, getHtmlForm().getLostChildren(), elements);
// If no form fields are found, browsers are able to find img elements by ID or name.
if (elements.isEmpty()) {
for (final DomNode node : getHtmlForm().getHtmlElementDescendants()) {
if (node instanceof HtmlImage) {
final HtmlImage img = (HtmlImage) node;
if (name.equals(img.getId()) || name.equals(img.getNameAttribute())) {
elements.add(img);
}
}
}
}
return elements;
}
private void addElements(final String name, final Iterable nodes,
final List addTo) {
for (final HtmlElement node : nodes) {
if (isAccessibleByIdOrName(node, name)) {
addTo.add(node);
}
}
}
/**
* Indicates if the element can be reached by id or name in expressions like "myForm.myField".
* @param element the element to test
* @param name the name used to address the element
* @return {@code true} if this element matches the conditions
*/
private boolean isAccessibleByIdOrName(final HtmlElement element, final String name) {
if (element instanceof FormFieldWithNameHistory && !(element instanceof HtmlImageInput)) {
if (element.getEnclosingForm() != getHtmlForm()) {
return false; // nested forms
}
if (name.equals(element.getId())) {
return true;
}
final FormFieldWithNameHistory elementWithNames = (FormFieldWithNameHistory) element;
if (getBrowserVersion().hasFeature(FORMFIELD_REACHABLE_BY_ORIGINAL_NAME)) {
if (name.equals(elementWithNames.getOriginalName())) {
return true;
}
}
else if (name.equals(element.getAttributeDirect("name"))) {
return true;
}
if (getBrowserVersion().hasFeature(FORMFIELD_REACHABLE_BY_NEW_NAMES)
&& elementWithNames.getNewNames().contains(name)) {
return true;
}
}
return false;
}
/**
* Returns the specified indexed property.
* @param index the index of the property
* @param start the scriptable object that was originally queried for this property
* @return the property
*/
@Override
public Object get(final int index, final Scriptable start) {
if (getDomNodeOrNull() == null) {
return NOT_FOUND; // typically for the prototype
}
return getElements().get(index, ((HTMLFormElement) start).getElements());
}
/**
* {@inheritDoc}
*/
@Override
public Object call(final Context cx, final Scriptable scope, final Scriptable thisObj, final Object[] args) {
if (!getBrowserVersion().hasFeature(JS_FORM_USABLE_AS_FUNCTION)) {
throw Context.reportRuntimeError("Not a function.");
}
if (args.length > 0) {
final Object arg = args[0];
if (arg instanceof String) {
return ScriptableObject.getProperty(this, (String) arg);
}
else if (arg instanceof Number) {
return ScriptableObject.getProperty(this, ((Number) arg).intValue());
}
}
return Undefined.instance;
}
/**
* {@inheritDoc}
*/
@Override
public Scriptable construct(final Context cx, final Scriptable scope, final Object[] args) {
if (!getBrowserVersion().hasFeature(JS_FORM_USABLE_AS_FUNCTION)) {
throw Context.reportRuntimeError("Not a function.");
}
return null;
}
@Override
public boolean dispatchEvent(final Event event) {
final boolean result = super.dispatchEvent(event);
if (Event.TYPE_SUBMIT.equals(event.getType())
&& getBrowserVersion().hasFeature(JS_FORM_DISPATCHEVENT_SUBMITS)) {
submit();
}
return result;
}
/**
* Checks whether the element has any constraints and whether it satisfies them.
* @return if the element is valid
*/
@JsxFunction
public boolean checkValidity() {
return getDomNodeOrDie().isValid();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy