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.html;
16
17 import static org.htmlunit.BrowserVersionFeatures.HTMLBUTTON_WILL_VALIDATE_IGNORES_READONLY;
18 import static org.htmlunit.html.HtmlForm.ATTRIBUTE_FORMNOVALIDATE;
19
20 import java.io.IOException;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashSet;
24 import java.util.Map;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.htmlunit.SgmlPage;
29 import org.htmlunit.javascript.host.event.Event;
30 import org.htmlunit.javascript.host.event.MouseEvent;
31 import org.htmlunit.util.NameValuePair;
32 import org.htmlunit.util.StringUtils;
33 import org.w3c.dom.Node;
34
35 /**
36 * Wrapper for the HTML element "button".
37 *
38 * @author Mike Bowler
39 * @author David K. Taylor
40 * @author Christian Sell
41 * @author David D. Kilzer
42 * @author Daniel Gredler
43 * @author Ahmed Ashour
44 * @author Dmitri Zoubkov
45 * @author Ronald Brill
46 * @author Frank Danek
47 * @author Sven Strickroth
48 */
49 public class HtmlButton extends HtmlElement implements DisabledElement, SubmittableElement,
50 LabelableElement, FormFieldWithNameHistory, ValidatableElement {
51
52 private static final Log LOG = LogFactory.getLog(HtmlButton.class);
53
54 /** The HTML tag represented by this element. */
55 public static final String TAG_NAME = "button";
56
57 private static final String TYPE_SUBMIT = "submit";
58 private static final String TYPE_RESET = "reset";
59 private static final String TYPE_BUTTON = "button";
60
61 private final String originalName_;
62 private Collection<String> newNames_ = Collections.emptySet();
63 private String customValidity_;
64
65 /**
66 * Creates a new instance.
67 *
68 * @param qualifiedName the qualified name of the element type to instantiate
69 * @param page the page that contains this element
70 * @param attributes the initial attributes
71 */
72 HtmlButton(final String qualifiedName, final SgmlPage page,
73 final Map<String, DomAttr> attributes) {
74 super(qualifiedName, page, attributes);
75 originalName_ = getNameAttribute();
76 }
77
78 /**
79 * Sets the content of the {@code value} attribute.
80 *
81 * @param newValue the new content
82 */
83 public void setValueAttribute(final String newValue) {
84 setAttribute(VALUE_ATTRIBUTE, newValue);
85 }
86
87 /**
88 * {@inheritDoc}
89 */
90 @Override
91 protected boolean doClickStateUpdate(final boolean shiftKey, final boolean ctrlKey) throws IOException {
92 if (!isDisabled()) {
93 final HtmlForm form = getEnclosingForm();
94 if (form != null) {
95 final String type = getType();
96 if (TYPE_BUTTON.equals(type)) {
97 return false;
98 }
99
100 if (TYPE_RESET.equals(type)) {
101 form.reset();
102 return false;
103 }
104
105 form.submit(this);
106 return false;
107 }
108 }
109
110 super.doClickStateUpdate(shiftKey, ctrlKey);
111 return false;
112 }
113
114 /**
115 * {@inheritDoc}
116 */
117 @Override
118 public final boolean isDisabled() {
119 if (hasAttribute(ATTRIBUTE_DISABLED)) {
120 return true;
121 }
122
123 Node node = getParentNode();
124 while (node != null) {
125 if (node instanceof DisabledElement
126 && ((DisabledElement) node).isDisabled()) {
127 return true;
128 }
129 node = node.getParentNode();
130 }
131
132 return false;
133 }
134
135 /**
136 * Returns {@code true} if this element is read only.
137 * @return {@code true} if this element is read only
138 */
139 public boolean isReadOnly() {
140 return hasAttribute("readOnly");
141 }
142
143 /**
144 * {@inheritDoc}
145 */
146 @Override
147 public NameValuePair[] getSubmitNameValuePairs() {
148 return new NameValuePair[]{new NameValuePair(getNameAttribute(), getValueAttribute())};
149 }
150
151 /**
152 * {@inheritDoc}
153 *
154 * @see SubmittableElement#reset()
155 */
156 @Override
157 public void reset() {
158 LOG.debug("reset() not implemented for this element");
159 }
160
161 /**
162 * {@inheritDoc}
163 *
164 * @see SubmittableElement#setDefaultValue(String)
165 */
166 @Override
167 public void setDefaultValue(final String defaultValue) {
168 LOG.debug("setDefaultValue() not implemented for this element");
169 }
170
171 /**
172 * {@inheritDoc}
173 *
174 * @see SubmittableElement#getDefaultValue()
175 */
176 @Override
177 public String getDefaultValue() {
178 LOG.debug("getDefaultValue() not implemented for this element");
179 return "";
180 }
181
182 /**
183 * {@inheritDoc}
184 *
185 * This implementation is empty; only checkboxes and radio buttons really care what the
186 * default checked value is.
187 *
188 * @see SubmittableElement#setDefaultChecked(boolean)
189 * @see HtmlRadioButtonInput#setDefaultChecked(boolean)
190 * @see HtmlCheckBoxInput#setDefaultChecked(boolean)
191 */
192 @Override
193 public void setDefaultChecked(final boolean defaultChecked) {
194 // Empty.
195 }
196
197 /**
198 * {@inheritDoc}
199 *
200 * This implementation returns {@code false}; only checkboxes and radio buttons really care what
201 * the default checked value is.
202 *
203 * @see SubmittableElement#isDefaultChecked()
204 * @see HtmlRadioButtonInput#isDefaultChecked()
205 * @see HtmlCheckBoxInput#isDefaultChecked()
206 */
207 @Override
208 public boolean isDefaultChecked() {
209 return false;
210 }
211
212 /**
213 * {@inheritDoc}
214 */
215 @Override
216 public boolean handles(final Event event) {
217 if (event instanceof MouseEvent) {
218 return true;
219 }
220
221 return super.handles(event);
222 }
223
224 /**
225 * Returns the value of the attribute {@code name}. Refer to the
226 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
227 * documentation for details on the use of this attribute.
228 *
229 * @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
230 */
231 public final String getNameAttribute() {
232 return getAttributeDirect(NAME_ATTRIBUTE);
233 }
234
235 /**
236 * Returns the value of the attribute {@code value}. Refer to the
237 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
238 * documentation for details on the use of this attribute.
239 *
240 * @return the value of the attribute {@code value} or an empty string if that attribute isn't defined
241 */
242 public final String getValueAttribute() {
243 return getAttributeDirect(VALUE_ATTRIBUTE);
244 }
245
246 /**
247 * Returns the value of the attribute {@code type}. Refer to the
248 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
249 * documentation for details on the use of this attribute.
250 *
251 * @return the value of the attribute {@code type} or the default value if that attribute isn't defined
252 */
253 public final String getTypeAttribute() {
254 return getAttribute(TYPE_ATTRIBUTE);
255 }
256
257 /**
258 * @return the normalized type value (submit|reset|button).
259 */
260 public String getType() {
261 final String type = getTypeAttribute();
262 if (TYPE_RESET.equalsIgnoreCase(type)) {
263 return TYPE_RESET;
264 }
265 if (TYPE_BUTTON.equalsIgnoreCase(type)) {
266 return TYPE_BUTTON;
267 }
268 return TYPE_SUBMIT;
269 }
270
271 /**
272 * Returns the value of the attribute {@code disabled}. Refer to the
273 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
274 * documentation for details on the use of this attribute.
275 *
276 * @return the value of the attribute {@code disabled} or an empty string if that attribute isn't defined
277 */
278 @Override
279 public final String getDisabledAttribute() {
280 return getAttributeDirect(ATTRIBUTE_DISABLED);
281 }
282
283 /**
284 * Returns the value of the attribute {@code tabindex}. Refer to the
285 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
286 * documentation for details on the use of this attribute.
287 *
288 * @return the value of the attribute {@code tabindex} or an empty string if that attribute isn't defined
289 */
290 public final String getTabIndexAttribute() {
291 return getAttributeDirect("tabindex");
292 }
293
294 /**
295 * Returns the value of the attribute {@code accesskey}. Refer to the
296 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
297 * documentation for details on the use of this attribute.
298 *
299 * @return the value of the attribute {@code accesskey} or an empty string if that attribute isn't defined
300 */
301 public final String getAccessKeyAttribute() {
302 return getAttributeDirect("accesskey");
303 }
304
305 /**
306 * Returns the value of the attribute {@code onfocus}. Refer to the
307 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
308 * documentation for details on the use of this attribute.
309 *
310 * @return the value of the attribute {@code onfocus} or an empty string if that attribute isn't defined
311 */
312 public final String getOnFocusAttribute() {
313 return getAttributeDirect("onfocus");
314 }
315
316 /**
317 * Returns the value of the attribute {@code onblur}. Refer to the
318 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
319 * documentation for details on the use of this attribute.
320 *
321 * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
322 */
323 public final String getOnBlurAttribute() {
324 return getAttributeDirect("onblur");
325 }
326
327 /**
328 * {@inheritDoc}
329 */
330 @Override
331 protected void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue,
332 final boolean notifyAttributeChangeListeners, final boolean notifyMutationObservers) {
333 final String qualifiedNameLC = StringUtils.toRootLowerCase(qualifiedName);
334 if (NAME_ATTRIBUTE.equals(qualifiedNameLC)) {
335 if (newNames_.isEmpty()) {
336 newNames_ = new HashSet<>();
337 }
338 newNames_.add(attributeValue);
339 }
340 super.setAttributeNS(namespaceURI, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
341 notifyMutationObservers);
342 }
343
344 /**
345 * {@inheritDoc}
346 */
347 @Override
348 public String getOriginalName() {
349 return originalName_;
350 }
351
352 /**
353 * {@inheritDoc}
354 */
355 @Override
356 public Collection<String> getNewNames() {
357 return newNames_;
358 }
359
360 /**
361 * {@inheritDoc}
362 */
363 @Override
364 public DisplayStyle getDefaultStyleDisplay() {
365 return DisplayStyle.INLINE_BLOCK;
366 }
367
368 /**
369 * {@inheritDoc}
370 * @return {@code true} to make generated XML readable as HTML.
371 */
372 @Override
373 protected boolean isEmptyXmlTagExpanded() {
374 return true;
375 }
376
377 /**
378 * {@inheritDoc}
379 */
380 @Override
381 public boolean isValid() {
382 if (TYPE_RESET.equals(getType())) {
383 return true;
384 }
385
386 return super.isValid() && !isCustomErrorValidityState();
387 }
388
389 /**
390 * {@inheritDoc}
391 */
392 @Override
393 public boolean willValidate() {
394 if (TYPE_RESET.equals(getType()) || TYPE_BUTTON.equals(getType())) {
395 return false;
396 }
397
398 return !isDisabled()
399 && (hasFeature(HTMLBUTTON_WILL_VALIDATE_IGNORES_READONLY) || !isReadOnly());
400 }
401
402 /**
403 * {@inheritDoc}
404 */
405 @Override
406 public void setCustomValidity(final String message) {
407 customValidity_ = message;
408 }
409
410 /**
411 * {@inheritDoc}
412 */
413 @Override
414 public boolean isCustomErrorValidityState() {
415 return !StringUtils.isEmptyOrNull(customValidity_);
416 }
417
418 @Override
419 public boolean isValidValidityState() {
420 return !isCustomErrorValidityState();
421 }
422
423 /**
424 * @return the value of the attribute {@code formnovalidate} or an empty string if that attribute isn't defined
425 */
426 public final boolean isFormNoValidate() {
427 return hasAttribute(ATTRIBUTE_FORMNOVALIDATE);
428 }
429
430 /**
431 * Sets the value of the attribute {@code formnovalidate}.
432 *
433 * @param noValidate the value of the attribute {@code formnovalidate}
434 */
435 public final void setFormNoValidate(final boolean noValidate) {
436 if (noValidate) {
437 setAttribute(ATTRIBUTE_FORMNOVALIDATE, ATTRIBUTE_FORMNOVALIDATE);
438 }
439 else {
440 removeAttribute(ATTRIBUTE_FORMNOVALIDATE);
441 }
442 }
443 }