1 /*
2 * Copyright (c) 2002-2026 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 org.htmlunit.javascript.host.html.ValidatableHTMLElement;
18
19 /**
20 * An {@link HtmlElement} that supports client-side validation using the Constraint Validation API.
21 * This uses {@link ValidatableHTMLElement}.
22 *
23 * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/Constraint_validation">
24 * Constraint Validation API</a>
25 *
26 * @author Ronald Brill
27 */
28 public interface ValidatableHtmlElement {
29
30 /**
31 * Returns whether the element is a candidate for constraint validation.
32 *
33 * @return whether the element is a candidate for constraint validation
34 */
35 boolean willValidate();
36
37 /**
38 * Sets the custom validity message for the element.
39 *
40 * @param message the new message
41 */
42 void setCustomValidity(String message);
43
44 /**
45 * Returns whether the user has provided input that the browser is unable to convert.
46 *
47 * @return {@code true} if the user has provided input that the browser is unable to convert
48 */
49 default boolean hasBadInputValidityState() {
50 return false;
51 }
52
53 /**
54 * Returns whether the element has a custom validity message.
55 *
56 * @return {@code true} if the element's custom validity message has been set to a non-empty
57 * string by calling {@link #setCustomValidity(String)}
58 */
59 boolean isCustomErrorValidityState();
60
61 /**
62 * Returns whether the element's value does not match the specified pattern.
63 *
64 * @return {@code true} if the value does not match the specified pattern;
65 * if {@code true}, the element matches the {@code :invalid} CSS pseudo-class
66 */
67 default boolean hasPatternMismatchValidityState() {
68 return false;
69 }
70
71 /**
72 * Returns whether the element's value does not satisfy the {@code step} constraint.
73 *
74 * @return {@code true} if the value does not fit the rules determined by the
75 * {@code step} attribute; if {@code true}, the element matches the
76 * {@code :invalid} and {@code :out-of-range} CSS pseudo-classes
77 */
78 default boolean isStepMismatchValidityState() {
79 return false;
80 }
81
82 /**
83 * Returns whether the element's value exceeds the maximum allowed length.
84 *
85 * @return {@code true} if the value is longer than the maximum length specified by the
86 * {@code maxlength} attribute; if {@code true}, the element matches the
87 * {@code :invalid} CSS pseudo-class
88 */
89 default boolean isTooLongValidityState() {
90 return false;
91 }
92
93 /**
94 * Returns whether the element's value is shorter than the minimum required length.
95 *
96 * @return {@code true} if the value is shorter than the minimum length specified by the
97 * {@code minlength} attribute; if {@code true}, the element matches the
98 * {@code :invalid} CSS pseudo-class
99 */
100 default boolean isTooShortValidityState() {
101 return false;
102 }
103
104 /**
105 * Returns whether the element's value has a type mismatch.
106 *
107 * @return {@code true} if the value is not in the required syntax (for example,
108 * for {@code email} or {@code url}); if {@code true}, the element matches the
109 * {@code :invalid} CSS pseudo-class
110 */
111 default boolean hasTypeMismatchValidityState() {
112 return false;
113 }
114
115 /**
116 * Returns whether the element's value exceeds the maximum allowed value.
117 *
118 * @return {@code true} if the value is greater than the maximum specified by the
119 * {@code max} attribute; if {@code true}, the element matches the
120 * {@code :invalid} and {@code :out-of-range} CSS pseudo-classes
121 */
122 default boolean hasRangeOverflowValidityState() {
123 return false;
124 }
125
126 /**
127 * Returns whether the element's value is less than the minimum allowed value.
128 *
129 * @return {@code true} if the value is less than the minimum specified by the
130 * {@code min} attribute; if {@code true}, the element matches the
131 * {@code :invalid} and {@code :out-of-range} CSS pseudo-classes
132 */
133 default boolean hasRangeUnderflowValidityState() {
134 return false;
135 }
136
137 /**
138 * Returns whether the element satisfies all validation constraints.
139 *
140 * @return {@code true} if the element is valid; if {@code true}, the element matches the
141 * {@code :valid} CSS pseudo-class, otherwise the {@code :invalid} CSS pseudo-class
142 */
143 boolean isValidValidityState();
144
145 /**
146 * Returns whether the element is missing a required value.
147 *
148 * @return {@code true} if the element has a required attribute but no value;
149 * if {@code true}, the element matches the {@code :invalid} CSS pseudo-class
150 */
151 default boolean isValueMissingValidityState() {
152 return false;
153 }
154
155 /**
156 * Returns the raw custom validity message set via {@link #setCustomValidity(String)},
157 * or the empty string if none has been set.
158 * @return the custom validity message
159 */
160 String getCustomValidity();
161
162 /**
163 * Returns the (potentially control-specific) message describing the
164 * currently failing constraint, in spec priority order:
165 * customError, patternMismatch, rangeOverflow, rangeUnderflow,
166 * stepMismatch, tooLong, tooShort, typeMismatch, badInput, valueMissing.
167 * Returns "" if barred from validation or currently valid.
168 * @return the validation message
169 */
170 default String getValidationMessage() {
171 if (!willValidate() || isValidValidityState()) {
172 return "";
173 }
174 if (isCustomErrorValidityState()) {
175 return getCustomValidity();
176 }
177 if (hasPatternMismatchValidityState()) {
178 return getPatternMismatchMessage();
179 }
180 if (hasRangeOverflowValidityState()) {
181 return getRangeOverflowMessage();
182 }
183 if (hasRangeUnderflowValidityState()) {
184 return getRangeUnderflowMessage();
185 }
186 if (isStepMismatchValidityState()) {
187 return getStepMismatchMessage();
188 }
189 if (isTooLongValidityState()) {
190 return getTooLongMessage();
191 }
192 if (isTooShortValidityState()) {
193 return getTooShortMessage();
194 }
195 if (hasTypeMismatchValidityState()) {
196 return getTypeMismatchMessage();
197 }
198 if (hasBadInputValidityState()) {
199 return getBadInputMessage();
200 }
201 if (isValueMissingValidityState()) {
202 return getValueMissingMessage();
203 }
204 return "";
205 }
206
207 /**
208 * Returns the message describing a pattern mismatch (the element's value
209 * does not match the constraint specified by the {@code pattern}
210 * attribute). Generic, uniform wording -- override in a concrete class
211 * that can interpolate control-specific details (e.g. the {@code pattern}
212 * itself or a {@code title} hint) into the message.
213 * @return the pattern mismatch message
214 */
215 default String getPatternMismatchMessage() {
216 return "Please match the requested format.";
217 }
218
219 /**
220 * Returns the message describing a range overflow (the element's value is
221 * greater than the maximum specified by the {@code max} attribute).
222 * Generic, uniform wording -- override in a concrete class that can
223 * interpolate the actual {@code max} value into the message.
224 * @return the range overflow message
225 */
226 default String getRangeOverflowMessage() {
227 return "Value must be less than or equal to the maximum.";
228 }
229
230 /**
231 * Returns the message describing a range underflow (the element's value is
232 * less than the minimum specified by the {@code min} attribute). Generic,
233 * uniform wording -- override in a concrete class that can interpolate the
234 * actual {@code min} value into the message.
235 * @return the range underflow message
236 */
237 default String getRangeUnderflowMessage() {
238 return "Value must be greater than or equal to the minimum.";
239 }
240
241 /**
242 * Returns the message describing a step mismatch (the element's value does
243 * not satisfy the constraint specified by the {@code step} attribute).
244 * Generic fallback wording -- real browsers typically also name the two
245 * nearest step-aligned valid values, which this default does not attempt
246 * to compute; override in a concrete class if that level of detail is
247 * needed.
248 * @return the step mismatch message
249 */
250 default String getStepMismatchMessage() {
251 return "Please enter a valid value.";
252 }
253
254 /**
255 * Returns the message describing a too-long value (the element's value is
256 * longer than the maximum length specified by the {@code maxlength}
257 * attribute). Generic, uniform wording -- override in a concrete class
258 * that can interpolate the actual {@code maxlength} and current length
259 * into the message.
260 * @return the too-long message
261 */
262 default String getTooLongMessage() {
263 return "Please shorten this text.";
264 }
265
266 /**
267 * Returns the message describing a too-short value (the element's value is
268 * shorter than the minimum length specified by the {@code minlength}
269 * attribute). Generic, uniform wording -- override in a concrete class
270 * that can interpolate the actual {@code minlength} and current length
271 * into the message.
272 * @return the too-short message
273 */
274 default String getTooShortMessage() {
275 return "Please lengthen this text.";
276 }
277
278 /**
279 * Returns the message describing a type mismatch (the element's value is
280 * not in the syntax required by its {@code type}, e.g. {@code email} or
281 * {@code url}). Generic, uniform wording -- override in a concrete class
282 * that can produce a type-specific message.
283 * @return the type mismatch message
284 */
285 default String getTypeMismatchMessage() {
286 return "Please enter a valid value.";
287 }
288
289 /**
290 * Returns the message describing bad input (the user has provided input
291 * that the browser is unable to convert, e.g. non-numeric text typed into
292 * a {@code number} input). Generic, uniform wording.
293 * @return the bad input message
294 */
295 default String getBadInputMessage() {
296 return "Please enter a valid value.";
297 }
298
299 /**
300 * Returns the message describing a missing required value (the element has
301 * a {@code required} attribute but no value). Generic, uniform wording.
302 * @return the value missing message
303 */
304 default String getValueMissingMessage() {
305 return "Please fill out this field.";
306 }
307 }