1 | /* |
2 | * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
3 | * (C) 1999 Antti Koivisto (koivisto@kde.org) |
4 | * (C) 2001 Dirk Mueller (mueller@kde.org) |
5 | * Copyright (C) 2004-2018 Apple Inc. All rights reserved. |
6 | * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
7 | * Copyright (C) 2007 Samuel Weinig (sam@webkit.org) |
8 | * Copyright (C) 2010 Google Inc. All rights reserved. |
9 | * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) |
10 | * Copyright (C) 2012 Samsung Electronics. All rights reserved. |
11 | * |
12 | * This library is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU Library General Public |
14 | * License as published by the Free Software Foundation; either |
15 | * version 2 of the License, or (at your option) any later version. |
16 | * |
17 | * This library is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
20 | * Library General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU Library General Public License |
23 | * along with this library; see the file COPYING.LIB. If not, write to |
24 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
25 | * Boston, MA 02110-1301, USA. |
26 | * |
27 | */ |
28 | |
29 | #include "config.h" |
30 | #include "HTMLInputElement.h" |
31 | |
32 | #include "AXObjectCache.h" |
33 | #include "BeforeTextInsertedEvent.h" |
34 | #include "CSSGradientValue.h" |
35 | #include "CSSPropertyNames.h" |
36 | #include "CSSValuePool.h" |
37 | #include "Chrome.h" |
38 | #include "ChromeClient.h" |
39 | #include "DateTimeChooser.h" |
40 | #include "Document.h" |
41 | #include "Editor.h" |
42 | #include "EventNames.h" |
43 | #include "FileInputType.h" |
44 | #include "FileList.h" |
45 | #include "FormController.h" |
46 | #include "Frame.h" |
47 | #include "FrameSelection.h" |
48 | #include "FrameView.h" |
49 | #include "HTMLDataListElement.h" |
50 | #include "HTMLFormElement.h" |
51 | #include "HTMLImageLoader.h" |
52 | #include "HTMLOptionElement.h" |
53 | #include "HTMLParserIdioms.h" |
54 | #include "IdTargetObserver.h" |
55 | #include "KeyboardEvent.h" |
56 | #include "LocalizedStrings.h" |
57 | #include "MouseEvent.h" |
58 | #include "Page.h" |
59 | #include "PlatformMouseEvent.h" |
60 | #include "RenderTextControlSingleLine.h" |
61 | #include "RenderTheme.h" |
62 | #include "ScopedEventQueue.h" |
63 | #include "SearchInputType.h" |
64 | #include "Settings.h" |
65 | #include "StyleResolver.h" |
66 | #include "TextControlInnerElements.h" |
67 | #include <wtf/IsoMallocInlines.h> |
68 | #include <wtf/Language.h> |
69 | #include <wtf/MathExtras.h> |
70 | #include <wtf/Ref.h> |
71 | |
72 | #if ENABLE(TOUCH_EVENTS) |
73 | #include "TouchEvent.h" |
74 | #endif |
75 | |
76 | namespace WebCore { |
77 | |
78 | WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLInputElement); |
79 | |
80 | using namespace HTMLNames; |
81 | |
82 | #if ENABLE(DATALIST_ELEMENT) |
83 | class ListAttributeTargetObserver : IdTargetObserver { |
84 | WTF_MAKE_FAST_ALLOCATED; |
85 | public: |
86 | ListAttributeTargetObserver(const AtomicString& id, HTMLInputElement*); |
87 | |
88 | void idTargetChanged() override; |
89 | |
90 | private: |
91 | HTMLInputElement* m_element; |
92 | }; |
93 | #endif |
94 | |
95 | // FIXME: According to HTML4, the length attribute's value can be arbitrarily |
96 | // large. However, due to https://bugs.webkit.org/show_bug.cgi?id=14536 things |
97 | // get rather sluggish when a text field has a larger number of characters than |
98 | // this, even when just clicking in the text field. |
99 | const unsigned HTMLInputElement::maxEffectiveLength = 524288; |
100 | const int defaultSize = 20; |
101 | const int maxSavedResults = 256; |
102 | |
103 | HTMLInputElement::HTMLInputElement(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) |
104 | : HTMLTextFormControlElement(tagName, document, form) |
105 | , m_size(defaultSize) |
106 | , m_isChecked(false) |
107 | , m_dirtyCheckednessFlag(false) |
108 | , m_isIndeterminate(false) |
109 | , m_hasType(false) |
110 | , m_isActivatedSubmit(false) |
111 | , m_autocomplete(Uninitialized) |
112 | , m_isAutoFilled(false) |
113 | , m_autoFillButtonType(static_cast<uint8_t>(AutoFillButtonType::None)) |
114 | , m_lastAutoFillButtonType(static_cast<uint8_t>(AutoFillButtonType::None)) |
115 | , m_isAutoFillAvailable(false) |
116 | #if ENABLE(DATALIST_ELEMENT) |
117 | , m_hasNonEmptyList(false) |
118 | #endif |
119 | , m_stateRestored(false) |
120 | , m_parsingInProgress(createdByParser) |
121 | , m_valueAttributeWasUpdatedAfterParsing(false) |
122 | , m_wasModifiedByUser(false) |
123 | , m_canReceiveDroppedFiles(false) |
124 | #if ENABLE(TOUCH_EVENTS) |
125 | , m_hasTouchEventHandler(false) |
126 | #endif |
127 | , m_isSpellcheckDisabledExceptTextReplacement(false) |
128 | { |
129 | // m_inputType is lazily created when constructed by the parser to avoid constructing unnecessarily a text inputType and |
130 | // its shadow subtree, just to destroy them when the |type| attribute gets set by the parser to something else than 'text'. |
131 | if (!createdByParser) |
132 | m_inputType = InputType::createText(*this); |
133 | |
134 | ASSERT(hasTagName(inputTag)); |
135 | setHasCustomStyleResolveCallbacks(); |
136 | } |
137 | |
138 | Ref<HTMLInputElement> HTMLInputElement::create(const QualifiedName& tagName, Document& document, HTMLFormElement* form, bool createdByParser) |
139 | { |
140 | bool shouldCreateShadowRootLazily = createdByParser; |
141 | Ref<HTMLInputElement> inputElement = adoptRef(*new HTMLInputElement(tagName, document, form, createdByParser)); |
142 | if (!shouldCreateShadowRootLazily) |
143 | inputElement->ensureUserAgentShadowRoot(); |
144 | return inputElement; |
145 | } |
146 | |
147 | HTMLImageLoader& HTMLInputElement::ensureImageLoader() |
148 | { |
149 | if (!m_imageLoader) |
150 | m_imageLoader = std::make_unique<HTMLImageLoader>(*this); |
151 | return *m_imageLoader; |
152 | } |
153 | |
154 | void HTMLInputElement::didAddUserAgentShadowRoot(ShadowRoot&) |
155 | { |
156 | Ref<InputType> protectedInputType(*m_inputType); |
157 | protectedInputType->createShadowSubtree(); |
158 | updateInnerTextElementEditability(); |
159 | } |
160 | |
161 | HTMLInputElement::~HTMLInputElement() |
162 | { |
163 | if (needsSuspensionCallback()) |
164 | document().unregisterForDocumentSuspensionCallbacks(*this); |
165 | |
166 | // Need to remove form association while this is still an HTMLInputElement |
167 | // so that virtual functions are called correctly. |
168 | setForm(nullptr); |
169 | |
170 | // This is needed for a radio button that was not in a form, and also for |
171 | // a radio button that was in a form. The call to setForm(nullptr) above |
172 | // actually adds the button to the document groups in the latter case. |
173 | // That is inelegant, but harmless since we remove it here. |
174 | if (isRadioButton()) |
175 | document().formController().radioButtonGroups().removeButton(*this); |
176 | |
177 | #if ENABLE(TOUCH_EVENTS) |
178 | if (m_hasTouchEventHandler) |
179 | document().didRemoveEventTargetNode(*this); |
180 | #endif |
181 | } |
182 | |
183 | const AtomicString& HTMLInputElement::name() const |
184 | { |
185 | return m_name.isNull() ? emptyAtom() : m_name; |
186 | } |
187 | |
188 | Vector<FileChooserFileInfo> HTMLInputElement::filesFromFileInputFormControlState(const FormControlState& state) |
189 | { |
190 | return FileInputType::filesFromFormControlState(state); |
191 | } |
192 | |
193 | HTMLElement* HTMLInputElement::containerElement() const |
194 | { |
195 | return m_inputType->containerElement(); |
196 | } |
197 | |
198 | RefPtr<TextControlInnerTextElement> HTMLInputElement::innerTextElement() const |
199 | { |
200 | return m_inputType->innerTextElement(); |
201 | } |
202 | |
203 | HTMLElement* HTMLInputElement::innerBlockElement() const |
204 | { |
205 | return m_inputType->innerBlockElement(); |
206 | } |
207 | |
208 | HTMLElement* HTMLInputElement::innerSpinButtonElement() const |
209 | { |
210 | return m_inputType->innerSpinButtonElement(); |
211 | } |
212 | |
213 | HTMLElement* HTMLInputElement::capsLockIndicatorElement() const |
214 | { |
215 | return m_inputType->capsLockIndicatorElement(); |
216 | } |
217 | |
218 | HTMLElement* HTMLInputElement::autoFillButtonElement() const |
219 | { |
220 | return m_inputType->autoFillButtonElement(); |
221 | } |
222 | |
223 | HTMLElement* HTMLInputElement::resultsButtonElement() const |
224 | { |
225 | return m_inputType->resultsButtonElement(); |
226 | } |
227 | |
228 | HTMLElement* HTMLInputElement::cancelButtonElement() const |
229 | { |
230 | return m_inputType->cancelButtonElement(); |
231 | } |
232 | |
233 | HTMLElement* HTMLInputElement::sliderThumbElement() const |
234 | { |
235 | return m_inputType->sliderThumbElement(); |
236 | } |
237 | |
238 | HTMLElement* HTMLInputElement::sliderTrackElement() const |
239 | { |
240 | return m_inputType->sliderTrackElement(); |
241 | } |
242 | |
243 | HTMLElement* HTMLInputElement::placeholderElement() const |
244 | { |
245 | return m_inputType->placeholderElement(); |
246 | } |
247 | |
248 | #if ENABLE(DATALIST_ELEMENT) |
249 | HTMLElement* HTMLInputElement::dataListButtonElement() const |
250 | { |
251 | return m_inputType->dataListButtonElement(); |
252 | } |
253 | #endif |
254 | |
255 | bool HTMLInputElement::shouldAutocomplete() const |
256 | { |
257 | if (m_autocomplete != Uninitialized) |
258 | return m_autocomplete == On; |
259 | return HTMLTextFormControlElement::shouldAutocomplete(); |
260 | } |
261 | |
262 | bool HTMLInputElement::isValidValue(const String& value) const |
263 | { |
264 | if (!m_inputType->canSetStringValue()) { |
265 | ASSERT_NOT_REACHED(); |
266 | return false; |
267 | } |
268 | return !m_inputType->typeMismatchFor(value) |
269 | && !m_inputType->stepMismatch(value) |
270 | && !m_inputType->rangeUnderflow(value) |
271 | && !m_inputType->rangeOverflow(value) |
272 | && !tooShort(value, IgnoreDirtyFlag) |
273 | && !tooLong(value, IgnoreDirtyFlag) |
274 | && !m_inputType->patternMismatch(value) |
275 | && !m_inputType->valueMissing(value); |
276 | } |
277 | |
278 | bool HTMLInputElement::tooShort() const |
279 | { |
280 | return willValidate() && tooShort(value(), CheckDirtyFlag); |
281 | } |
282 | |
283 | bool HTMLInputElement::tooLong() const |
284 | { |
285 | return willValidate() && tooLong(value(), CheckDirtyFlag); |
286 | } |
287 | |
288 | bool HTMLInputElement::typeMismatch() const |
289 | { |
290 | return willValidate() && m_inputType->typeMismatch(); |
291 | } |
292 | |
293 | bool HTMLInputElement::valueMissing() const |
294 | { |
295 | return willValidate() && m_inputType->valueMissing(value()); |
296 | } |
297 | |
298 | bool HTMLInputElement::hasBadInput() const |
299 | { |
300 | return willValidate() && m_inputType->hasBadInput(); |
301 | } |
302 | |
303 | bool HTMLInputElement::patternMismatch() const |
304 | { |
305 | return willValidate() && m_inputType->patternMismatch(value()); |
306 | } |
307 | |
308 | bool HTMLInputElement::tooShort(StringView value, NeedsToCheckDirtyFlag check) const |
309 | { |
310 | if (!supportsMinLength()) |
311 | return false; |
312 | |
313 | int min = minLength(); |
314 | if (min <= 0) |
315 | return false; |
316 | |
317 | if (check == CheckDirtyFlag) { |
318 | // Return false for the default value or a value set by a script even if |
319 | // it is shorter than minLength. |
320 | if (!hasDirtyValue() || !m_wasModifiedByUser) |
321 | return false; |
322 | } |
323 | |
324 | // The empty string is excluded from tooShort validation. |
325 | if (value.isEmpty()) |
326 | return false; |
327 | |
328 | // FIXME: The HTML specification says that the "number of characters" is measured using code-unit length. |
329 | return numGraphemeClusters(value) < static_cast<unsigned>(min); |
330 | } |
331 | |
332 | bool HTMLInputElement::tooLong(StringView value, NeedsToCheckDirtyFlag check) const |
333 | { |
334 | if (!supportsMaxLength()) |
335 | return false; |
336 | unsigned max = effectiveMaxLength(); |
337 | if (check == CheckDirtyFlag) { |
338 | // Return false for the default value or a value set by a script even if |
339 | // it is longer than maxLength. |
340 | if (!hasDirtyValue() || !m_wasModifiedByUser) |
341 | return false; |
342 | } |
343 | // FIXME: The HTML specification says that the "number of characters" is measured using code-unit length. |
344 | return numGraphemeClusters(value) > max; |
345 | } |
346 | |
347 | bool HTMLInputElement::rangeUnderflow() const |
348 | { |
349 | return willValidate() && m_inputType->rangeUnderflow(value()); |
350 | } |
351 | |
352 | bool HTMLInputElement::rangeOverflow() const |
353 | { |
354 | return willValidate() && m_inputType->rangeOverflow(value()); |
355 | } |
356 | |
357 | String HTMLInputElement::validationMessage() const |
358 | { |
359 | if (!willValidate()) |
360 | return String(); |
361 | |
362 | if (customError()) |
363 | return customValidationMessage(); |
364 | |
365 | return m_inputType->validationMessage(); |
366 | } |
367 | |
368 | double HTMLInputElement::minimum() const |
369 | { |
370 | return m_inputType->minimum(); |
371 | } |
372 | |
373 | double HTMLInputElement::maximum() const |
374 | { |
375 | return m_inputType->maximum(); |
376 | } |
377 | |
378 | bool HTMLInputElement::stepMismatch() const |
379 | { |
380 | return willValidate() && m_inputType->stepMismatch(value()); |
381 | } |
382 | |
383 | bool HTMLInputElement::isValid() const |
384 | { |
385 | if (!willValidate()) |
386 | return true; |
387 | |
388 | String value = this->value(); |
389 | bool someError = m_inputType->typeMismatch() || m_inputType->stepMismatch(value) || m_inputType->rangeUnderflow(value) || m_inputType->rangeOverflow(value) |
390 | || tooShort(value, CheckDirtyFlag) || tooLong(value, CheckDirtyFlag) || m_inputType->patternMismatch(value) || m_inputType->valueMissing(value) |
391 | || m_inputType->hasBadInput() || customError(); |
392 | return !someError; |
393 | } |
394 | |
395 | bool HTMLInputElement::getAllowedValueStep(Decimal* step) const |
396 | { |
397 | return m_inputType->getAllowedValueStep(step); |
398 | } |
399 | |
400 | StepRange HTMLInputElement::createStepRange(AnyStepHandling anyStepHandling) const |
401 | { |
402 | return m_inputType->createStepRange(anyStepHandling); |
403 | } |
404 | |
405 | #if ENABLE(DATALIST_ELEMENT) |
406 | Optional<Decimal> HTMLInputElement::findClosestTickMarkValue(const Decimal& value) |
407 | { |
408 | return m_inputType->findClosestTickMarkValue(value); |
409 | } |
410 | #endif |
411 | |
412 | ExceptionOr<void> HTMLInputElement::stepUp(int n) |
413 | { |
414 | return m_inputType->stepUp(n); |
415 | } |
416 | |
417 | ExceptionOr<void> HTMLInputElement::stepDown(int n) |
418 | { |
419 | return m_inputType->stepUp(-n); |
420 | } |
421 | |
422 | void HTMLInputElement::blur() |
423 | { |
424 | m_inputType->blur(); |
425 | } |
426 | |
427 | void HTMLInputElement::defaultBlur() |
428 | { |
429 | HTMLTextFormControlElement::blur(); |
430 | } |
431 | |
432 | bool HTMLInputElement::hasCustomFocusLogic() const |
433 | { |
434 | return m_inputType->hasCustomFocusLogic(); |
435 | } |
436 | |
437 | bool HTMLInputElement::isKeyboardFocusable(KeyboardEvent* event) const |
438 | { |
439 | return m_inputType->isKeyboardFocusable(event); |
440 | } |
441 | |
442 | bool HTMLInputElement::isMouseFocusable() const |
443 | { |
444 | return m_inputType->isMouseFocusable(); |
445 | } |
446 | |
447 | bool HTMLInputElement::isTextFormControlFocusable() const |
448 | { |
449 | return HTMLTextFormControlElement::isFocusable(); |
450 | } |
451 | |
452 | bool HTMLInputElement::isTextFormControlKeyboardFocusable(KeyboardEvent* event) const |
453 | { |
454 | return HTMLTextFormControlElement::isKeyboardFocusable(event); |
455 | } |
456 | |
457 | bool HTMLInputElement::isTextFormControlMouseFocusable() const |
458 | { |
459 | return HTMLTextFormControlElement::isMouseFocusable(); |
460 | } |
461 | |
462 | void HTMLInputElement::updateFocusAppearance(SelectionRestorationMode restorationMode, SelectionRevealMode revealMode) |
463 | { |
464 | if (isTextField()) { |
465 | if (restorationMode == SelectionRestorationMode::SetDefault || !hasCachedSelection()) |
466 | setDefaultSelectionAfterFocus(revealMode); |
467 | else |
468 | restoreCachedSelection(revealMode); |
469 | } else |
470 | HTMLTextFormControlElement::updateFocusAppearance(restorationMode, revealMode); |
471 | } |
472 | |
473 | void HTMLInputElement::setDefaultSelectionAfterFocus(SelectionRevealMode revealMode) |
474 | { |
475 | ASSERT(isTextField()); |
476 | int start = 0; |
477 | auto direction = SelectionHasNoDirection; |
478 | auto* frame = document().frame(); |
479 | if (frame && frame->editor().behavior().shouldMoveSelectionToEndWhenFocusingTextInput()) { |
480 | start = std::numeric_limits<int>::max(); |
481 | direction = SelectionHasForwardDirection; |
482 | } |
483 | setSelectionRange(start, std::numeric_limits<int>::max(), direction, revealMode, Element::defaultFocusTextStateChangeIntent()); |
484 | } |
485 | |
486 | void HTMLInputElement::endEditing() |
487 | { |
488 | if (!isTextField()) |
489 | return; |
490 | |
491 | if (RefPtr<Frame> frame = document().frame()) |
492 | frame->editor().textFieldDidEndEditing(this); |
493 | } |
494 | |
495 | bool HTMLInputElement::shouldUseInputMethod() |
496 | { |
497 | return m_inputType->shouldUseInputMethod(); |
498 | } |
499 | |
500 | void HTMLInputElement::handleFocusEvent(Node* oldFocusedNode, FocusDirection direction) |
501 | { |
502 | m_inputType->handleFocusEvent(oldFocusedNode, direction); |
503 | } |
504 | |
505 | void HTMLInputElement::handleBlurEvent() |
506 | { |
507 | m_inputType->handleBlurEvent(); |
508 | } |
509 | |
510 | void HTMLInputElement::setType(const AtomicString& type) |
511 | { |
512 | setAttributeWithoutSynchronization(typeAttr, type); |
513 | } |
514 | |
515 | void HTMLInputElement::resignStrongPasswordAppearance() |
516 | { |
517 | if (!hasAutoFillStrongPasswordButton()) |
518 | return; |
519 | setAutoFilled(false); |
520 | setShowAutoFillButton(AutoFillButtonType::None); |
521 | if (auto* page = document().page()) |
522 | page->chrome().client().inputElementDidResignStrongPasswordAppearance(*this); |
523 | } |
524 | |
525 | void HTMLInputElement::updateType() |
526 | { |
527 | ASSERT(m_inputType); |
528 | auto newType = InputType::create(*this, attributeWithoutSynchronization(typeAttr)); |
529 | m_hasType = true; |
530 | if (m_inputType->formControlType() == newType->formControlType()) |
531 | return; |
532 | |
533 | removeFromRadioButtonGroup(); |
534 | resignStrongPasswordAppearance(); |
535 | |
536 | bool didStoreValue = m_inputType->storesValueSeparateFromAttribute(); |
537 | bool willStoreValue = newType->storesValueSeparateFromAttribute(); |
538 | bool neededSuspensionCallback = needsSuspensionCallback(); |
539 | bool didRespectHeightAndWidth = m_inputType->shouldRespectHeightAndWidthAttributes(); |
540 | bool wasSuccessfulSubmitButtonCandidate = m_inputType->canBeSuccessfulSubmitButton(); |
541 | |
542 | if (didStoreValue && !willStoreValue && hasDirtyValue()) { |
543 | setAttributeWithoutSynchronization(valueAttr, m_valueIfDirty); |
544 | m_valueIfDirty = String(); |
545 | } |
546 | |
547 | m_inputType->destroyShadowSubtree(); |
548 | m_inputType->detachFromElement(); |
549 | |
550 | m_inputType = WTFMove(newType); |
551 | m_inputType->createShadowSubtree(); |
552 | updateInnerTextElementEditability(); |
553 | |
554 | setNeedsWillValidateCheck(); |
555 | |
556 | if (!didStoreValue && willStoreValue) |
557 | m_valueIfDirty = sanitizeValue(attributeWithoutSynchronization(valueAttr)); |
558 | else |
559 | updateValueIfNeeded(); |
560 | |
561 | setFormControlValueMatchesRenderer(false); |
562 | m_inputType->updateInnerTextValue(); |
563 | |
564 | m_wasModifiedByUser = false; |
565 | |
566 | if (neededSuspensionCallback) |
567 | unregisterForSuspensionCallbackIfNeeded(); |
568 | else |
569 | registerForSuspensionCallbackIfNeeded(); |
570 | |
571 | if (didRespectHeightAndWidth != m_inputType->shouldRespectHeightAndWidthAttributes()) { |
572 | ASSERT(elementData()); |
573 | // FIXME: We don't have the old attribute values so we pretend that we didn't have the old values. |
574 | if (const Attribute* height = findAttributeByName(heightAttr)) |
575 | attributeChanged(heightAttr, nullAtom(), height->value()); |
576 | if (const Attribute* width = findAttributeByName(widthAttr)) |
577 | attributeChanged(widthAttr, nullAtom(), width->value()); |
578 | if (const Attribute* align = findAttributeByName(alignAttr)) |
579 | attributeChanged(alignAttr, nullAtom(), align->value()); |
580 | } |
581 | |
582 | if (form() && wasSuccessfulSubmitButtonCandidate != m_inputType->canBeSuccessfulSubmitButton()) |
583 | form()->resetDefaultButton(); |
584 | |
585 | runPostTypeUpdateTasks(); |
586 | } |
587 | |
588 | inline void HTMLInputElement::runPostTypeUpdateTasks() |
589 | { |
590 | ASSERT(m_inputType); |
591 | #if ENABLE(TOUCH_EVENTS) |
592 | bool hasTouchEventHandler = m_inputType->hasTouchEventHandler(); |
593 | if (hasTouchEventHandler != m_hasTouchEventHandler) { |
594 | if (hasTouchEventHandler) |
595 | document().didAddTouchEventHandler(*this); |
596 | else |
597 | document().didRemoveTouchEventHandler(*this); |
598 | m_hasTouchEventHandler = hasTouchEventHandler; |
599 | } |
600 | #endif |
601 | |
602 | if (renderer()) |
603 | invalidateStyleAndRenderersForSubtree(); |
604 | |
605 | if (document().focusedElement() == this) |
606 | updateFocusAppearance(SelectionRestorationMode::Restore, SelectionRevealMode::Reveal); |
607 | |
608 | setChangedSinceLastFormControlChangeEvent(false); |
609 | |
610 | addToRadioButtonGroup(); |
611 | |
612 | updateValidity(); |
613 | } |
614 | |
615 | void HTMLInputElement::subtreeHasChanged() |
616 | { |
617 | m_inputType->subtreeHasChanged(); |
618 | // When typing in an input field, childrenChanged is not called, so we need to force the directionality check. |
619 | calculateAndAdjustDirectionality(); |
620 | } |
621 | |
622 | const AtomicString& HTMLInputElement::formControlType() const |
623 | { |
624 | return m_inputType->formControlType(); |
625 | } |
626 | |
627 | bool HTMLInputElement::shouldSaveAndRestoreFormControlState() const |
628 | { |
629 | if (!m_inputType->shouldSaveAndRestoreFormControlState()) |
630 | return false; |
631 | return HTMLTextFormControlElement::shouldSaveAndRestoreFormControlState(); |
632 | } |
633 | |
634 | FormControlState HTMLInputElement::saveFormControlState() const |
635 | { |
636 | return m_inputType->saveFormControlState(); |
637 | } |
638 | |
639 | void HTMLInputElement::restoreFormControlState(const FormControlState& state) |
640 | { |
641 | m_inputType->restoreFormControlState(state); |
642 | m_stateRestored = true; |
643 | } |
644 | |
645 | bool HTMLInputElement::canStartSelection() const |
646 | { |
647 | if (!isTextField()) |
648 | return false; |
649 | return HTMLTextFormControlElement::canStartSelection(); |
650 | } |
651 | |
652 | bool HTMLInputElement::canHaveSelection() const |
653 | { |
654 | return isTextField(); |
655 | } |
656 | |
657 | void HTMLInputElement::accessKeyAction(bool sendMouseEvents) |
658 | { |
659 | Ref<InputType> protectedInputType(*m_inputType); |
660 | protectedInputType->accessKeyAction(sendMouseEvents); |
661 | } |
662 | |
663 | bool HTMLInputElement::isPresentationAttribute(const QualifiedName& name) const |
664 | { |
665 | if (name == vspaceAttr || name == hspaceAttr || name == widthAttr || name == heightAttr || (name == borderAttr && isImageButton())) |
666 | return true; |
667 | return HTMLTextFormControlElement::isPresentationAttribute(name); |
668 | } |
669 | |
670 | void HTMLInputElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style) |
671 | { |
672 | if (name == vspaceAttr) { |
673 | addHTMLLengthToStyle(style, CSSPropertyMarginTop, value); |
674 | addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value); |
675 | } else if (name == hspaceAttr) { |
676 | addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value); |
677 | addHTMLLengthToStyle(style, CSSPropertyMarginRight, value); |
678 | } else if (name == alignAttr) { |
679 | if (m_inputType->shouldRespectAlignAttribute()) |
680 | applyAlignmentAttributeToStyle(value, style); |
681 | } else if (name == widthAttr) { |
682 | if (m_inputType->shouldRespectHeightAndWidthAttributes()) |
683 | addHTMLLengthToStyle(style, CSSPropertyWidth, value); |
684 | } else if (name == heightAttr) { |
685 | if (m_inputType->shouldRespectHeightAndWidthAttributes()) |
686 | addHTMLLengthToStyle(style, CSSPropertyHeight, value); |
687 | } else if (name == borderAttr && isImageButton()) |
688 | applyBorderAttributeToStyle(value, style); |
689 | else |
690 | HTMLTextFormControlElement::collectStyleForPresentationAttribute(name, value, style); |
691 | } |
692 | |
693 | inline void HTMLInputElement::initializeInputType() |
694 | { |
695 | ASSERT(m_parsingInProgress); |
696 | ASSERT(!m_inputType); |
697 | |
698 | const AtomicString& type = attributeWithoutSynchronization(typeAttr); |
699 | if (type.isNull()) { |
700 | m_inputType = InputType::createText(*this); |
701 | ensureUserAgentShadowRoot(); |
702 | setNeedsWillValidateCheck(); |
703 | return; |
704 | } |
705 | |
706 | m_hasType = true; |
707 | m_inputType = InputType::create(*this, type); |
708 | ensureUserAgentShadowRoot(); |
709 | setNeedsWillValidateCheck(); |
710 | registerForSuspensionCallbackIfNeeded(); |
711 | runPostTypeUpdateTasks(); |
712 | } |
713 | |
714 | void HTMLInputElement::parseAttribute(const QualifiedName& name, const AtomicString& value) |
715 | { |
716 | ASSERT(m_inputType); |
717 | Ref<InputType> protectedInputType(*m_inputType); |
718 | |
719 | if (name == nameAttr) { |
720 | removeFromRadioButtonGroup(); |
721 | m_name = value; |
722 | addToRadioButtonGroup(); |
723 | HTMLTextFormControlElement::parseAttribute(name, value); |
724 | } else if (name == autocompleteAttr) { |
725 | if (equalLettersIgnoringASCIICase(value, "off" )) { |
726 | m_autocomplete = Off; |
727 | registerForSuspensionCallbackIfNeeded(); |
728 | } else { |
729 | bool needsToUnregister = m_autocomplete == Off; |
730 | |
731 | if (value.isEmpty()) |
732 | m_autocomplete = Uninitialized; |
733 | else |
734 | m_autocomplete = On; |
735 | |
736 | if (needsToUnregister) |
737 | unregisterForSuspensionCallbackIfNeeded(); |
738 | } |
739 | } else if (name == typeAttr) |
740 | updateType(); |
741 | else if (name == valueAttr) { |
742 | // Changes to the value attribute may change whether or not this element has a default value. |
743 | // If this field is autocomplete=off that might affect the return value of needsSuspensionCallback. |
744 | if (m_autocomplete == Off) { |
745 | unregisterForSuspensionCallbackIfNeeded(); |
746 | registerForSuspensionCallbackIfNeeded(); |
747 | } |
748 | // We only need to setChanged if the form is looking at the default value right now. |
749 | if (!hasDirtyValue()) { |
750 | updatePlaceholderVisibility(); |
751 | invalidateStyleForSubtree(); |
752 | } |
753 | setFormControlValueMatchesRenderer(false); |
754 | updateValidity(); |
755 | m_valueAttributeWasUpdatedAfterParsing = !m_parsingInProgress; |
756 | } else if (name == checkedAttr) { |
757 | if (m_inputType->isCheckable()) |
758 | invalidateStyleForSubtree(); |
759 | |
760 | // Another radio button in the same group might be checked by state |
761 | // restore. We shouldn't call setChecked() even if this has the checked |
762 | // attribute. So, delay the setChecked() call until |
763 | // finishParsingChildren() is called if parsing is in progress. |
764 | if ((!m_parsingInProgress || !document().formController().hasFormStateToRestore()) && !m_dirtyCheckednessFlag) { |
765 | setChecked(!value.isNull()); |
766 | // setChecked() above sets the dirty checkedness flag so we need to reset it. |
767 | m_dirtyCheckednessFlag = false; |
768 | } |
769 | } else if (name == maxlengthAttr) |
770 | maxLengthAttributeChanged(value); |
771 | else if (name == minlengthAttr) |
772 | minLengthAttributeChanged(value); |
773 | else if (name == sizeAttr) { |
774 | unsigned oldSize = m_size; |
775 | m_size = limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(value, defaultSize); |
776 | if (m_size != oldSize && renderer()) |
777 | renderer()->setNeedsLayoutAndPrefWidthsRecalc(); |
778 | } else if (name == resultsAttr) |
779 | m_maxResults = !value.isNull() ? std::min(value.toInt(), maxSavedResults) : -1; |
780 | else if (name == autosaveAttr || name == incrementalAttr) |
781 | invalidateStyleForSubtree(); |
782 | else if (name == maxAttr || name == minAttr || name == multipleAttr || name == patternAttr || name == precisionAttr || name == stepAttr) |
783 | updateValidity(); |
784 | #if ENABLE(DATALIST_ELEMENT) |
785 | else if (name == listAttr) { |
786 | m_hasNonEmptyList = !value.isEmpty(); |
787 | if (m_hasNonEmptyList) { |
788 | resetListAttributeTargetObserver(); |
789 | listAttributeTargetChanged(); |
790 | } |
791 | } |
792 | #endif |
793 | else |
794 | HTMLTextFormControlElement::parseAttribute(name, value); |
795 | |
796 | m_inputType->attributeChanged(name); |
797 | } |
798 | |
799 | void HTMLInputElement::disabledStateChanged() |
800 | { |
801 | HTMLTextFormControlElement::disabledStateChanged(); |
802 | m_inputType->disabledStateChanged(); |
803 | } |
804 | |
805 | void HTMLInputElement::readOnlyStateChanged() |
806 | { |
807 | HTMLTextFormControlElement::readOnlyStateChanged(); |
808 | m_inputType->readOnlyStateChanged(); |
809 | } |
810 | |
811 | void HTMLInputElement::parserDidSetAttributes() |
812 | { |
813 | ASSERT(m_parsingInProgress); |
814 | initializeInputType(); |
815 | } |
816 | |
817 | void HTMLInputElement::finishParsingChildren() |
818 | { |
819 | m_parsingInProgress = false; |
820 | ASSERT(m_inputType); |
821 | HTMLTextFormControlElement::finishParsingChildren(); |
822 | if (!m_stateRestored) { |
823 | bool checked = hasAttributeWithoutSynchronization(checkedAttr); |
824 | if (checked) |
825 | setChecked(checked); |
826 | m_dirtyCheckednessFlag = false; |
827 | } |
828 | } |
829 | |
830 | bool HTMLInputElement::rendererIsNeeded(const RenderStyle& style) |
831 | { |
832 | return m_inputType->rendererIsNeeded() && HTMLTextFormControlElement::rendererIsNeeded(style); |
833 | } |
834 | |
835 | RenderPtr<RenderElement> HTMLInputElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&) |
836 | { |
837 | return m_inputType->createInputRenderer(WTFMove(style)); |
838 | } |
839 | |
840 | void HTMLInputElement::willAttachRenderers() |
841 | { |
842 | if (!m_hasType) |
843 | updateType(); |
844 | } |
845 | |
846 | void HTMLInputElement::didAttachRenderers() |
847 | { |
848 | HTMLTextFormControlElement::didAttachRenderers(); |
849 | |
850 | m_inputType->attach(); |
851 | |
852 | if (document().focusedElement() == this) { |
853 | document().view()->queuePostLayoutCallback([protectedThis = makeRef(*this)] { |
854 | protectedThis->updateFocusAppearance(SelectionRestorationMode::Restore, SelectionRevealMode::Reveal); |
855 | }); |
856 | } |
857 | } |
858 | |
859 | void HTMLInputElement::didDetachRenderers() |
860 | { |
861 | setFormControlValueMatchesRenderer(false); |
862 | m_inputType->detach(); |
863 | } |
864 | |
865 | String HTMLInputElement::altText() const |
866 | { |
867 | // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen |
868 | // also heavily discussed by Hixie on bugzilla |
869 | // note this is intentionally different to HTMLImageElement::altText() |
870 | String alt = attributeWithoutSynchronization(altAttr); |
871 | // fall back to title attribute |
872 | if (alt.isNull()) |
873 | alt = attributeWithoutSynchronization(titleAttr); |
874 | if (alt.isNull()) |
875 | alt = attributeWithoutSynchronization(valueAttr); |
876 | if (alt.isEmpty()) |
877 | alt = inputElementAltText(); |
878 | return alt; |
879 | } |
880 | |
881 | bool HTMLInputElement::isSuccessfulSubmitButton() const |
882 | { |
883 | // HTML spec says that buttons must have names to be considered successful. |
884 | // However, other browsers do not impose this constraint. So we do not. |
885 | return !isDisabledFormControl() && m_inputType->canBeSuccessfulSubmitButton(); |
886 | } |
887 | |
888 | bool HTMLInputElement::matchesDefaultPseudoClass() const |
889 | { |
890 | ASSERT(m_inputType); |
891 | if (m_inputType->canBeSuccessfulSubmitButton()) |
892 | return !isDisabledFormControl() && form() && form()->defaultButton() == this; |
893 | return m_inputType->isCheckable() && hasAttributeWithoutSynchronization(checkedAttr); |
894 | } |
895 | |
896 | bool HTMLInputElement::isActivatedSubmit() const |
897 | { |
898 | return m_isActivatedSubmit; |
899 | } |
900 | |
901 | void HTMLInputElement::setActivatedSubmit(bool flag) |
902 | { |
903 | m_isActivatedSubmit = flag; |
904 | } |
905 | |
906 | bool HTMLInputElement::appendFormData(DOMFormData& formData, bool multipart) |
907 | { |
908 | Ref<InputType> protectedInputType(*m_inputType); |
909 | return m_inputType->isFormDataAppendable() && m_inputType->appendFormData(formData, multipart); |
910 | } |
911 | |
912 | void HTMLInputElement::reset() |
913 | { |
914 | if (m_inputType->storesValueSeparateFromAttribute()) |
915 | setValue(String()); |
916 | |
917 | setAutoFilled(false); |
918 | setShowAutoFillButton(AutoFillButtonType::None); |
919 | setChecked(hasAttributeWithoutSynchronization(checkedAttr)); |
920 | m_dirtyCheckednessFlag = false; |
921 | } |
922 | |
923 | bool HTMLInputElement::isTextField() const |
924 | { |
925 | return m_inputType->isTextField(); |
926 | } |
927 | |
928 | bool HTMLInputElement::isTextType() const |
929 | { |
930 | return m_inputType->isTextType(); |
931 | } |
932 | |
933 | void HTMLInputElement::setChecked(bool nowChecked) |
934 | { |
935 | if (checked() == nowChecked) |
936 | return; |
937 | |
938 | m_dirtyCheckednessFlag = true; |
939 | m_isChecked = nowChecked; |
940 | invalidateStyleForSubtree(); |
941 | |
942 | if (RadioButtonGroups* buttons = radioButtonGroups()) |
943 | buttons->updateCheckedState(*this); |
944 | if (renderer() && renderer()->style().hasAppearance()) |
945 | renderer()->theme().stateChanged(*renderer(), ControlStates::CheckedState); |
946 | updateValidity(); |
947 | |
948 | // Ideally we'd do this from the render tree (matching |
949 | // RenderTextView), but it's not possible to do it at the moment |
950 | // because of the way the code is structured. |
951 | if (renderer()) { |
952 | if (AXObjectCache* cache = renderer()->document().existingAXObjectCache()) |
953 | cache->checkedStateChanged(this); |
954 | } |
955 | |
956 | invalidateStyleForSubtree(); |
957 | } |
958 | |
959 | void HTMLInputElement::setIndeterminate(bool newValue) |
960 | { |
961 | if (indeterminate() == newValue) |
962 | return; |
963 | |
964 | m_isIndeterminate = newValue; |
965 | |
966 | invalidateStyleForSubtree(); |
967 | |
968 | if (renderer() && renderer()->style().hasAppearance()) |
969 | renderer()->theme().stateChanged(*renderer(), ControlStates::CheckedState); |
970 | } |
971 | |
972 | unsigned HTMLInputElement::size() const |
973 | { |
974 | return m_size; |
975 | } |
976 | |
977 | bool HTMLInputElement::sizeShouldIncludeDecoration(int& preferredSize) const |
978 | { |
979 | return m_inputType->sizeShouldIncludeDecoration(defaultSize, preferredSize); |
980 | } |
981 | |
982 | float HTMLInputElement::decorationWidth() const |
983 | { |
984 | return m_inputType->decorationWidth(); |
985 | } |
986 | |
987 | void HTMLInputElement::copyNonAttributePropertiesFromElement(const Element& source) |
988 | { |
989 | auto& sourceElement = downcast<HTMLInputElement>(source); |
990 | |
991 | m_valueIfDirty = sourceElement.m_valueIfDirty; |
992 | m_wasModifiedByUser = false; |
993 | setChecked(sourceElement.m_isChecked); |
994 | m_dirtyCheckednessFlag = sourceElement.m_dirtyCheckednessFlag; |
995 | m_isIndeterminate = sourceElement.m_isIndeterminate; |
996 | |
997 | HTMLTextFormControlElement::copyNonAttributePropertiesFromElement(source); |
998 | |
999 | updateValidity(); |
1000 | setFormControlValueMatchesRenderer(false); |
1001 | m_inputType->updateInnerTextValue(); |
1002 | } |
1003 | |
1004 | String HTMLInputElement::value() const |
1005 | { |
1006 | String value; |
1007 | if (m_inputType->getTypeSpecificValue(value)) |
1008 | return value; |
1009 | |
1010 | value = m_valueIfDirty; |
1011 | if (!value.isNull()) |
1012 | return value; |
1013 | |
1014 | auto& valueString = attributeWithoutSynchronization(valueAttr); |
1015 | value = sanitizeValue(valueString); |
1016 | if (!value.isNull()) |
1017 | return value; |
1018 | |
1019 | return m_inputType->fallbackValue(); |
1020 | } |
1021 | |
1022 | String HTMLInputElement::valueWithDefault() const |
1023 | { |
1024 | String value = this->value(); |
1025 | if (!value.isNull()) |
1026 | return value; |
1027 | |
1028 | return m_inputType->defaultValue(); |
1029 | } |
1030 | |
1031 | void HTMLInputElement::setValueForUser(const String& value) |
1032 | { |
1033 | // Call setValue and make it send a change event. |
1034 | setValue(value, DispatchChangeEvent); |
1035 | } |
1036 | |
1037 | ExceptionOr<void> HTMLInputElement::setValue(const String& value, TextFieldEventBehavior eventBehavior) |
1038 | { |
1039 | if (isFileUpload() && !value.isEmpty()) |
1040 | return Exception { InvalidStateError }; |
1041 | |
1042 | if (!m_inputType->canSetValue(value)) |
1043 | return { }; |
1044 | |
1045 | Ref<HTMLInputElement> protectedThis(*this); |
1046 | EventQueueScope scope; |
1047 | String sanitizedValue = sanitizeValue(value); |
1048 | bool valueChanged = sanitizedValue != this->value(); |
1049 | |
1050 | setLastChangeWasNotUserEdit(); |
1051 | setFormControlValueMatchesRenderer(false); |
1052 | m_inputType->setValue(sanitizedValue, valueChanged, eventBehavior); |
1053 | |
1054 | bool wasModifiedProgrammatically = eventBehavior == DispatchNoEvent; |
1055 | if (wasModifiedProgrammatically) |
1056 | resignStrongPasswordAppearance(); |
1057 | return { }; |
1058 | } |
1059 | |
1060 | void HTMLInputElement::setValueInternal(const String& sanitizedValue, TextFieldEventBehavior eventBehavior) |
1061 | { |
1062 | m_valueIfDirty = sanitizedValue; |
1063 | m_wasModifiedByUser = eventBehavior != DispatchNoEvent; |
1064 | updateValidity(); |
1065 | } |
1066 | |
1067 | double HTMLInputElement::valueAsDate() const |
1068 | { |
1069 | return m_inputType->valueAsDate(); |
1070 | } |
1071 | |
1072 | ExceptionOr<void> HTMLInputElement::setValueAsDate(double value) |
1073 | { |
1074 | return m_inputType->setValueAsDate(value); |
1075 | } |
1076 | |
1077 | double HTMLInputElement::valueAsNumber() const |
1078 | { |
1079 | return m_inputType->valueAsDouble(); |
1080 | } |
1081 | |
1082 | ExceptionOr<void> HTMLInputElement::setValueAsNumber(double newValue, TextFieldEventBehavior eventBehavior) |
1083 | { |
1084 | if (!std::isfinite(newValue)) |
1085 | return Exception { NotSupportedError }; |
1086 | return m_inputType->setValueAsDouble(newValue, eventBehavior); |
1087 | } |
1088 | |
1089 | void HTMLInputElement::setValueFromRenderer(const String& value) |
1090 | { |
1091 | // File upload controls will never use this. |
1092 | ASSERT(!isFileUpload()); |
1093 | |
1094 | // Renderer and our event handler are responsible for sanitizing values. |
1095 | // Input types that support the selection API do *not* sanitize their |
1096 | // user input in order to retain parity between what's in the model and |
1097 | // what's on the screen. |
1098 | ASSERT(m_inputType->supportsSelectionAPI() || value == sanitizeValue(value) || sanitizeValue(value).isEmpty()); |
1099 | |
1100 | // Workaround for bug where trailing \n is included in the result of textContent. |
1101 | // The assert macro above may also be simplified by removing the expression |
1102 | // that calls isEmpty. |
1103 | // http://bugs.webkit.org/show_bug.cgi?id=9661 |
1104 | m_valueIfDirty = value == "\n" ? emptyString() : value; |
1105 | |
1106 | setFormControlValueMatchesRenderer(true); |
1107 | m_wasModifiedByUser = true; |
1108 | |
1109 | // Input event is fired by the Node::defaultEventHandler for editable controls. |
1110 | if (!isTextField()) |
1111 | dispatchInputEvent(); |
1112 | |
1113 | updateValidity(); |
1114 | |
1115 | // Clear auto fill flag (and yellow background) on user edit. |
1116 | setAutoFilled(false); |
1117 | } |
1118 | |
1119 | void HTMLInputElement::willDispatchEvent(Event& event, InputElementClickState& state) |
1120 | { |
1121 | if (event.type() == eventNames().textInputEvent && m_inputType->shouldSubmitImplicitly(event)) |
1122 | event.stopPropagation(); |
1123 | if (event.type() == eventNames().clickEvent && is<MouseEvent>(event) && downcast<MouseEvent>(event).button() == LeftButton) { |
1124 | m_inputType->willDispatchClick(state); |
1125 | state.stateful = true; |
1126 | } |
1127 | } |
1128 | |
1129 | void HTMLInputElement::didDispatchClickEvent(Event& event, const InputElementClickState& state) |
1130 | { |
1131 | m_inputType->didDispatchClick(event, state); |
1132 | } |
1133 | |
1134 | void HTMLInputElement::didBlur() |
1135 | { |
1136 | m_inputType->elementDidBlur(); |
1137 | } |
1138 | |
1139 | void HTMLInputElement::defaultEventHandler(Event& event) |
1140 | { |
1141 | if (is<MouseEvent>(event) && event.type() == eventNames().clickEvent && downcast<MouseEvent>(event).button() == LeftButton) { |
1142 | m_inputType->handleClickEvent(downcast<MouseEvent>(event)); |
1143 | if (event.defaultHandled()) |
1144 | return; |
1145 | } |
1146 | |
1147 | #if ENABLE(TOUCH_EVENTS) |
1148 | if (is<TouchEvent>(event)) { |
1149 | m_inputType->handleTouchEvent(downcast<TouchEvent>(event)); |
1150 | if (event.defaultHandled()) |
1151 | return; |
1152 | } |
1153 | #endif |
1154 | |
1155 | if (is<KeyboardEvent>(event) && event.type() == eventNames().keydownEvent) { |
1156 | auto shouldCallBaseEventHandler = m_inputType->handleKeydownEvent(downcast<KeyboardEvent>(event)); |
1157 | if (event.defaultHandled() || shouldCallBaseEventHandler == InputType::ShouldCallBaseEventHandler::No) |
1158 | return; |
1159 | } |
1160 | |
1161 | // Call the base event handler before any of our own event handling for almost all events in text fields. |
1162 | // Makes editing keyboard handling take precedence over the keydown and keypress handling in this function. |
1163 | bool callBaseClassEarly = isTextField() && (event.type() == eventNames().keydownEvent || event.type() == eventNames().keypressEvent); |
1164 | if (callBaseClassEarly) { |
1165 | HTMLTextFormControlElement::defaultEventHandler(event); |
1166 | if (event.defaultHandled()) |
1167 | return; |
1168 | } |
1169 | |
1170 | // DOMActivate events cause the input to be "activated" - in the case of image and submit inputs, this means |
1171 | // actually submitting the form. For reset inputs, the form is reset. These events are sent when the user clicks |
1172 | // on the element, or presses enter while it is the active element. JavaScript code wishing to activate the element |
1173 | // must dispatch a DOMActivate event - a click event will not do the job. |
1174 | if (event.type() == eventNames().DOMActivateEvent) { |
1175 | m_inputType->handleDOMActivateEvent(event); |
1176 | if (event.defaultHandled()) |
1177 | return; |
1178 | } |
1179 | |
1180 | // Use key press event here since sending simulated mouse events |
1181 | // on key down blocks the proper sending of the key press event. |
1182 | if (is<KeyboardEvent>(event)) { |
1183 | KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(event); |
1184 | if (keyboardEvent.type() == eventNames().keypressEvent) { |
1185 | m_inputType->handleKeypressEvent(keyboardEvent); |
1186 | if (keyboardEvent.defaultHandled()) |
1187 | return; |
1188 | } else if (keyboardEvent.type() == eventNames().keyupEvent) { |
1189 | m_inputType->handleKeyupEvent(keyboardEvent); |
1190 | if (keyboardEvent.defaultHandled()) |
1191 | return; |
1192 | } |
1193 | } |
1194 | |
1195 | if (m_inputType->shouldSubmitImplicitly(event)) { |
1196 | if (isSearchField()) { |
1197 | addSearchResult(); |
1198 | onSearch(); |
1199 | } |
1200 | // Form submission finishes editing, just as loss of focus does. |
1201 | // If there was a change, send the event now. |
1202 | if (wasChangedSinceLastFormControlChangeEvent()) |
1203 | dispatchFormControlChangeEvent(); |
1204 | |
1205 | // Form may never have been present, or may have been destroyed by code responding to the change event. |
1206 | if (auto formElement = makeRefPtr(form())) |
1207 | formElement->submitImplicitly(event, canTriggerImplicitSubmission()); |
1208 | |
1209 | event.setDefaultHandled(); |
1210 | return; |
1211 | } |
1212 | |
1213 | if (is<BeforeTextInsertedEvent>(event)) |
1214 | m_inputType->handleBeforeTextInsertedEvent(downcast<BeforeTextInsertedEvent>(event)); |
1215 | |
1216 | if (is<MouseEvent>(event) && event.type() == eventNames().mousedownEvent) { |
1217 | m_inputType->handleMouseDownEvent(downcast<MouseEvent>(event)); |
1218 | if (event.defaultHandled()) |
1219 | return; |
1220 | } |
1221 | |
1222 | m_inputType->forwardEvent(event); |
1223 | |
1224 | if (!callBaseClassEarly && !event.defaultHandled()) |
1225 | HTMLTextFormControlElement::defaultEventHandler(event); |
1226 | } |
1227 | |
1228 | bool HTMLInputElement::willRespondToMouseClickEvents() |
1229 | { |
1230 | if (!isDisabledFormControl()) |
1231 | return true; |
1232 | |
1233 | return HTMLTextFormControlElement::willRespondToMouseClickEvents(); |
1234 | } |
1235 | |
1236 | bool HTMLInputElement::isURLAttribute(const Attribute& attribute) const |
1237 | { |
1238 | return attribute.name() == srcAttr || attribute.name() == formactionAttr || HTMLTextFormControlElement::isURLAttribute(attribute); |
1239 | } |
1240 | |
1241 | String HTMLInputElement::defaultValue() const |
1242 | { |
1243 | return attributeWithoutSynchronization(valueAttr); |
1244 | } |
1245 | |
1246 | void HTMLInputElement::setDefaultValue(const String &value) |
1247 | { |
1248 | setAttributeWithoutSynchronization(valueAttr, value); |
1249 | } |
1250 | |
1251 | static inline bool isRFC2616TokenCharacter(UChar ch) |
1252 | { |
1253 | return isASCII(ch) && ch > ' ' && ch != '"' && ch != '(' && ch != ')' && ch != ',' && ch != '/' && (ch < ':' || ch > '@') && (ch < '[' || ch > ']') && ch != '{' && ch != '}' && ch != 0x7f; |
1254 | } |
1255 | |
1256 | static bool isValidMIMEType(const String& type) |
1257 | { |
1258 | size_t slashPosition = type.find('/'); |
1259 | if (slashPosition == notFound || !slashPosition || slashPosition == type.length() - 1) |
1260 | return false; |
1261 | for (size_t i = 0; i < type.length(); ++i) { |
1262 | if (!isRFC2616TokenCharacter(type[i]) && i != slashPosition) |
1263 | return false; |
1264 | } |
1265 | return true; |
1266 | } |
1267 | |
1268 | static bool isValidFileExtension(const String& type) |
1269 | { |
1270 | if (type.length() < 2) |
1271 | return false; |
1272 | return type[0] == '.'; |
1273 | } |
1274 | |
1275 | static Vector<String> parseAcceptAttribute(const String& acceptString, bool (*predicate)(const String&)) |
1276 | { |
1277 | Vector<String> types; |
1278 | if (acceptString.isEmpty()) |
1279 | return types; |
1280 | |
1281 | for (auto& splitType : acceptString.split(',')) { |
1282 | String trimmedType = stripLeadingAndTrailingHTMLSpaces(splitType); |
1283 | if (trimmedType.isEmpty()) |
1284 | continue; |
1285 | if (!predicate(trimmedType)) |
1286 | continue; |
1287 | types.append(trimmedType.convertToASCIILowercase()); |
1288 | } |
1289 | |
1290 | return types; |
1291 | } |
1292 | |
1293 | Vector<String> HTMLInputElement::acceptMIMETypes() |
1294 | { |
1295 | return parseAcceptAttribute(attributeWithoutSynchronization(acceptAttr), isValidMIMEType); |
1296 | } |
1297 | |
1298 | Vector<String> HTMLInputElement::acceptFileExtensions() |
1299 | { |
1300 | return parseAcceptAttribute(attributeWithoutSynchronization(acceptAttr), isValidFileExtension); |
1301 | } |
1302 | |
1303 | String HTMLInputElement::accept() const |
1304 | { |
1305 | return attributeWithoutSynchronization(acceptAttr); |
1306 | } |
1307 | |
1308 | String HTMLInputElement::alt() const |
1309 | { |
1310 | return attributeWithoutSynchronization(altAttr); |
1311 | } |
1312 | |
1313 | unsigned HTMLInputElement::effectiveMaxLength() const |
1314 | { |
1315 | // The number -1 represents no maximum at all; conveniently it becomes a super-large value when converted to unsigned. |
1316 | return std::min<unsigned>(maxLength(), maxEffectiveLength); |
1317 | } |
1318 | |
1319 | bool HTMLInputElement::multiple() const |
1320 | { |
1321 | return hasAttributeWithoutSynchronization(multipleAttr); |
1322 | } |
1323 | |
1324 | ExceptionOr<void> HTMLInputElement::setSize(unsigned size) |
1325 | { |
1326 | if (!size) |
1327 | return Exception { IndexSizeError }; |
1328 | setUnsignedIntegralAttribute(sizeAttr, limitToOnlyHTMLNonNegativeNumbersGreaterThanZero(size, defaultSize)); |
1329 | return { }; |
1330 | } |
1331 | |
1332 | URL HTMLInputElement::src() const |
1333 | { |
1334 | return document().completeURL(attributeWithoutSynchronization(srcAttr)); |
1335 | } |
1336 | |
1337 | void HTMLInputElement::setAutoFilled(bool autoFilled) |
1338 | { |
1339 | if (autoFilled == m_isAutoFilled) |
1340 | return; |
1341 | |
1342 | m_isAutoFilled = autoFilled; |
1343 | invalidateStyleForSubtree(); |
1344 | } |
1345 | |
1346 | void HTMLInputElement::setShowAutoFillButton(AutoFillButtonType autoFillButtonType) |
1347 | { |
1348 | if (static_cast<uint8_t>(autoFillButtonType) == m_autoFillButtonType) |
1349 | return; |
1350 | |
1351 | m_lastAutoFillButtonType = m_autoFillButtonType; |
1352 | m_autoFillButtonType = static_cast<uint8_t>(autoFillButtonType); |
1353 | m_inputType->updateAutoFillButton(); |
1354 | updateInnerTextElementEditability(); |
1355 | invalidateStyleForSubtree(); |
1356 | } |
1357 | |
1358 | FileList* HTMLInputElement::files() |
1359 | { |
1360 | return m_inputType->files(); |
1361 | } |
1362 | |
1363 | void HTMLInputElement::setFiles(RefPtr<FileList>&& files) |
1364 | { |
1365 | m_inputType->setFiles(WTFMove(files)); |
1366 | } |
1367 | |
1368 | #if ENABLE(DRAG_SUPPORT) |
1369 | bool HTMLInputElement::receiveDroppedFiles(const DragData& dragData) |
1370 | { |
1371 | return m_inputType->receiveDroppedFiles(dragData); |
1372 | } |
1373 | #endif |
1374 | |
1375 | Icon* HTMLInputElement::icon() const |
1376 | { |
1377 | return m_inputType->icon(); |
1378 | } |
1379 | |
1380 | String HTMLInputElement::displayString() const |
1381 | { |
1382 | return m_inputType->displayString(); |
1383 | } |
1384 | |
1385 | bool HTMLInputElement::canReceiveDroppedFiles() const |
1386 | { |
1387 | return m_canReceiveDroppedFiles; |
1388 | } |
1389 | |
1390 | void HTMLInputElement::setCanReceiveDroppedFiles(bool canReceiveDroppedFiles) |
1391 | { |
1392 | if (m_canReceiveDroppedFiles == canReceiveDroppedFiles) |
1393 | return; |
1394 | m_canReceiveDroppedFiles = canReceiveDroppedFiles; |
1395 | if (renderer()) |
1396 | renderer()->updateFromElement(); |
1397 | } |
1398 | |
1399 | String HTMLInputElement::visibleValue() const |
1400 | { |
1401 | return m_inputType->visibleValue(); |
1402 | } |
1403 | |
1404 | String HTMLInputElement::sanitizeValue(const String& proposedValue) const |
1405 | { |
1406 | if (proposedValue.isNull()) |
1407 | return proposedValue; |
1408 | return m_inputType->sanitizeValue(proposedValue); |
1409 | } |
1410 | |
1411 | String HTMLInputElement::localizeValue(const String& proposedValue) const |
1412 | { |
1413 | if (proposedValue.isNull()) |
1414 | return proposedValue; |
1415 | return m_inputType->localizeValue(proposedValue); |
1416 | } |
1417 | |
1418 | bool HTMLInputElement::isInRange() const |
1419 | { |
1420 | return willValidate() && m_inputType->isInRange(value()); |
1421 | } |
1422 | |
1423 | bool HTMLInputElement::isOutOfRange() const |
1424 | { |
1425 | return willValidate() && m_inputType->isOutOfRange(value()); |
1426 | } |
1427 | |
1428 | bool HTMLInputElement::needsSuspensionCallback() |
1429 | { |
1430 | if (m_inputType->shouldResetOnDocumentActivation()) |
1431 | return true; |
1432 | |
1433 | // Sensitive input elements are marked with autocomplete=off, and we want to wipe them out |
1434 | // when going back; returning true here arranges for us to call reset at the time |
1435 | // the page is restored. Non-empty textual default values indicate that the field |
1436 | // is not really sensitive -- there's no default value for an account number -- |
1437 | // and we would see unexpected results if we reset to something other than blank. |
1438 | bool isSensitive = m_autocomplete == Off && !(m_inputType->isTextType() && !defaultValue().isEmpty()); |
1439 | |
1440 | return isSensitive; |
1441 | } |
1442 | |
1443 | void HTMLInputElement::registerForSuspensionCallbackIfNeeded() |
1444 | { |
1445 | if (needsSuspensionCallback()) |
1446 | document().registerForDocumentSuspensionCallbacks(*this); |
1447 | } |
1448 | |
1449 | void HTMLInputElement::unregisterForSuspensionCallbackIfNeeded() |
1450 | { |
1451 | if (!needsSuspensionCallback()) |
1452 | document().unregisterForDocumentSuspensionCallbacks(*this); |
1453 | } |
1454 | |
1455 | bool HTMLInputElement::isRequiredFormControl() const |
1456 | { |
1457 | return m_inputType->supportsRequired() && isRequired(); |
1458 | } |
1459 | |
1460 | bool HTMLInputElement::matchesReadWritePseudoClass() const |
1461 | { |
1462 | return m_inputType->supportsReadOnly() && !isDisabledOrReadOnly(); |
1463 | } |
1464 | |
1465 | void HTMLInputElement::addSearchResult() |
1466 | { |
1467 | m_inputType->addSearchResult(); |
1468 | } |
1469 | |
1470 | void HTMLInputElement::onSearch() |
1471 | { |
1472 | // The type of the input element could have changed during event handling. If we are no longer |
1473 | // a search field, don't try to do search things. |
1474 | if (!isSearchField()) |
1475 | return; |
1476 | |
1477 | if (m_inputType) |
1478 | downcast<SearchInputType>(*m_inputType.get()).stopSearchEventTimer(); |
1479 | dispatchEvent(Event::create(eventNames().searchEvent, Event::CanBubble::Yes, Event::IsCancelable::No)); |
1480 | } |
1481 | |
1482 | void HTMLInputElement::resumeFromDocumentSuspension() |
1483 | { |
1484 | ASSERT(needsSuspensionCallback()); |
1485 | |
1486 | #if ENABLE(INPUT_TYPE_COLOR) |
1487 | // <input type=color> uses prepareForDocumentSuspension to detach the color picker UI, |
1488 | // so it should not be reset when being loaded from page cache. |
1489 | if (isColorControl()) |
1490 | return; |
1491 | #endif // ENABLE(INPUT_TYPE_COLOR) |
1492 | reset(); |
1493 | } |
1494 | |
1495 | #if ENABLE(INPUT_TYPE_COLOR) |
1496 | void HTMLInputElement::prepareForDocumentSuspension() |
1497 | { |
1498 | if (!isColorControl()) |
1499 | return; |
1500 | m_inputType->detach(); |
1501 | } |
1502 | #endif // ENABLE(INPUT_TYPE_COLOR) |
1503 | |
1504 | |
1505 | void HTMLInputElement::willChangeForm() |
1506 | { |
1507 | removeFromRadioButtonGroup(); |
1508 | HTMLTextFormControlElement::willChangeForm(); |
1509 | } |
1510 | |
1511 | void HTMLInputElement::didChangeForm() |
1512 | { |
1513 | HTMLTextFormControlElement::didChangeForm(); |
1514 | addToRadioButtonGroup(); |
1515 | } |
1516 | |
1517 | Node::InsertedIntoAncestorResult HTMLInputElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
1518 | { |
1519 | HTMLTextFormControlElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
1520 | #if ENABLE(DATALIST_ELEMENT) |
1521 | resetListAttributeTargetObserver(); |
1522 | #endif |
1523 | return InsertedIntoAncestorResult::NeedsPostInsertionCallback; |
1524 | } |
1525 | |
1526 | void HTMLInputElement::didFinishInsertingNode() |
1527 | { |
1528 | HTMLTextFormControlElement::didFinishInsertingNode(); |
1529 | if (isConnected() && !form()) |
1530 | addToRadioButtonGroup(); |
1531 | } |
1532 | |
1533 | void HTMLInputElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
1534 | { |
1535 | if (removalType.disconnectedFromDocument && !form()) |
1536 | removeFromRadioButtonGroup(); |
1537 | HTMLTextFormControlElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
1538 | ASSERT(!isConnected()); |
1539 | #if ENABLE(DATALIST_ELEMENT) |
1540 | resetListAttributeTargetObserver(); |
1541 | #endif |
1542 | } |
1543 | |
1544 | void HTMLInputElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument) |
1545 | { |
1546 | if (imageLoader()) |
1547 | imageLoader()->elementDidMoveToNewDocument(); |
1548 | |
1549 | // Always unregister for cache callbacks when leaving a document, even if we would otherwise like to be registered |
1550 | if (needsSuspensionCallback()) { |
1551 | oldDocument.unregisterForDocumentSuspensionCallbacks(*this); |
1552 | newDocument.registerForDocumentSuspensionCallbacks(*this); |
1553 | } |
1554 | |
1555 | // We call this even for radio buttons in forms; it's harmless because the |
1556 | // removeButton function is written to be safe for buttons not in any group. |
1557 | if (isRadioButton()) |
1558 | oldDocument.formController().radioButtonGroups().removeButton(*this); |
1559 | |
1560 | #if ENABLE(TOUCH_EVENTS) |
1561 | if (m_hasTouchEventHandler) { |
1562 | oldDocument.didRemoveEventTargetNode(*this); |
1563 | newDocument.didAddTouchEventHandler(*this); |
1564 | } |
1565 | #endif |
1566 | |
1567 | HTMLTextFormControlElement::didMoveToNewDocument(oldDocument, newDocument); |
1568 | } |
1569 | |
1570 | void HTMLInputElement::addSubresourceAttributeURLs(ListHashSet<URL>& urls) const |
1571 | { |
1572 | HTMLTextFormControlElement::addSubresourceAttributeURLs(urls); |
1573 | |
1574 | addSubresourceURL(urls, src()); |
1575 | } |
1576 | |
1577 | bool HTMLInputElement::computeWillValidate() const |
1578 | { |
1579 | return m_inputType->supportsValidation() && HTMLTextFormControlElement::computeWillValidate(); |
1580 | } |
1581 | |
1582 | void HTMLInputElement::requiredStateChanged() |
1583 | { |
1584 | HTMLTextFormControlElement::requiredStateChanged(); |
1585 | if (auto* buttons = radioButtonGroups()) |
1586 | buttons->requiredStateChanged(*this); |
1587 | m_inputType->requiredStateChanged(); |
1588 | } |
1589 | |
1590 | Color HTMLInputElement::valueAsColor() const |
1591 | { |
1592 | return m_inputType->valueAsColor(); |
1593 | } |
1594 | |
1595 | void HTMLInputElement::selectColor(StringView color) |
1596 | { |
1597 | m_inputType->selectColor(color); |
1598 | } |
1599 | |
1600 | Vector<Color> HTMLInputElement::suggestedColors() const |
1601 | { |
1602 | return m_inputType->suggestedColors(); |
1603 | } |
1604 | |
1605 | #if ENABLE(DATALIST_ELEMENT) |
1606 | |
1607 | RefPtr<HTMLElement> HTMLInputElement::list() const |
1608 | { |
1609 | return dataList(); |
1610 | } |
1611 | |
1612 | RefPtr<HTMLDataListElement> HTMLInputElement::dataList() const |
1613 | { |
1614 | if (!m_hasNonEmptyList) |
1615 | return nullptr; |
1616 | |
1617 | if (!m_inputType->shouldRespectListAttribute()) |
1618 | return nullptr; |
1619 | |
1620 | RefPtr<Element> element = treeScope().getElementById(attributeWithoutSynchronization(listAttr)); |
1621 | if (!is<HTMLDataListElement>(element)) |
1622 | return nullptr; |
1623 | |
1624 | return downcast<HTMLDataListElement>(element.get()); |
1625 | } |
1626 | |
1627 | void HTMLInputElement::resetListAttributeTargetObserver() |
1628 | { |
1629 | if (isConnected()) |
1630 | m_listAttributeTargetObserver = std::make_unique<ListAttributeTargetObserver>(attributeWithoutSynchronization(listAttr), this); |
1631 | else |
1632 | m_listAttributeTargetObserver = nullptr; |
1633 | } |
1634 | |
1635 | void HTMLInputElement::listAttributeTargetChanged() |
1636 | { |
1637 | m_inputType->listAttributeTargetChanged(); |
1638 | } |
1639 | |
1640 | #endif // ENABLE(DATALIST_ELEMENT) |
1641 | |
1642 | bool HTMLInputElement::isPresentingAttachedView() const |
1643 | { |
1644 | return m_inputType->isPresentingAttachedView(); |
1645 | } |
1646 | |
1647 | bool HTMLInputElement::isSteppable() const |
1648 | { |
1649 | return m_inputType->isSteppable(); |
1650 | } |
1651 | |
1652 | #if PLATFORM(IOS_FAMILY) |
1653 | DateComponents::Type HTMLInputElement::dateType() const |
1654 | { |
1655 | return m_inputType->dateType(); |
1656 | } |
1657 | #endif |
1658 | |
1659 | bool HTMLInputElement::isTextButton() const |
1660 | { |
1661 | return m_inputType->isTextButton(); |
1662 | } |
1663 | |
1664 | bool HTMLInputElement::isRadioButton() const |
1665 | { |
1666 | return m_inputType->isRadioButton(); |
1667 | } |
1668 | |
1669 | bool HTMLInputElement::isSearchField() const |
1670 | { |
1671 | return m_inputType->isSearchField(); |
1672 | } |
1673 | |
1674 | bool HTMLInputElement::isInputTypeHidden() const |
1675 | { |
1676 | return m_inputType->isHiddenType(); |
1677 | } |
1678 | |
1679 | bool HTMLInputElement::isPasswordField() const |
1680 | { |
1681 | return m_inputType->isPasswordField(); |
1682 | } |
1683 | |
1684 | bool HTMLInputElement::isCheckbox() const |
1685 | { |
1686 | return m_inputType->isCheckbox(); |
1687 | } |
1688 | |
1689 | bool HTMLInputElement::isRangeControl() const |
1690 | { |
1691 | return m_inputType->isRangeControl(); |
1692 | } |
1693 | |
1694 | #if ENABLE(INPUT_TYPE_COLOR) |
1695 | bool HTMLInputElement::isColorControl() const |
1696 | { |
1697 | return m_inputType->isColorControl(); |
1698 | } |
1699 | #endif |
1700 | |
1701 | bool HTMLInputElement::isText() const |
1702 | { |
1703 | return m_inputType->isTextType(); |
1704 | } |
1705 | |
1706 | bool HTMLInputElement::isEmailField() const |
1707 | { |
1708 | return m_inputType->isEmailField(); |
1709 | } |
1710 | |
1711 | bool HTMLInputElement::isFileUpload() const |
1712 | { |
1713 | return m_inputType->isFileUpload(); |
1714 | } |
1715 | |
1716 | bool HTMLInputElement::isImageButton() const |
1717 | { |
1718 | return m_inputType->isImageButton(); |
1719 | } |
1720 | |
1721 | bool HTMLInputElement::isNumberField() const |
1722 | { |
1723 | return m_inputType->isNumberField(); |
1724 | } |
1725 | |
1726 | bool HTMLInputElement::isSubmitButton() const |
1727 | { |
1728 | return m_inputType->isSubmitButton(); |
1729 | } |
1730 | |
1731 | bool HTMLInputElement::isTelephoneField() const |
1732 | { |
1733 | return m_inputType->isTelephoneField(); |
1734 | } |
1735 | |
1736 | bool HTMLInputElement::isURLField() const |
1737 | { |
1738 | return m_inputType->isURLField(); |
1739 | } |
1740 | |
1741 | bool HTMLInputElement::isDateField() const |
1742 | { |
1743 | return m_inputType->isDateField(); |
1744 | } |
1745 | |
1746 | bool HTMLInputElement::isDateTimeField() const |
1747 | { |
1748 | return m_inputType->isDateTimeField(); |
1749 | } |
1750 | |
1751 | bool HTMLInputElement::isDateTimeLocalField() const |
1752 | { |
1753 | return m_inputType->isDateTimeLocalField(); |
1754 | } |
1755 | |
1756 | bool HTMLInputElement::isMonthField() const |
1757 | { |
1758 | return m_inputType->isMonthField(); |
1759 | } |
1760 | |
1761 | bool HTMLInputElement::isTimeField() const |
1762 | { |
1763 | return m_inputType->isTimeField(); |
1764 | } |
1765 | |
1766 | bool HTMLInputElement::isWeekField() const |
1767 | { |
1768 | return m_inputType->isWeekField(); |
1769 | } |
1770 | |
1771 | bool HTMLInputElement::isEnumeratable() const |
1772 | { |
1773 | return m_inputType->isEnumeratable(); |
1774 | } |
1775 | |
1776 | bool HTMLInputElement::supportLabels() const |
1777 | { |
1778 | return m_inputType->supportLabels(); |
1779 | } |
1780 | |
1781 | bool HTMLInputElement::shouldAppearChecked() const |
1782 | { |
1783 | return checked() && m_inputType->isCheckable(); |
1784 | } |
1785 | |
1786 | bool HTMLInputElement::supportsPlaceholder() const |
1787 | { |
1788 | return m_inputType->supportsPlaceholder(); |
1789 | } |
1790 | |
1791 | void HTMLInputElement::updatePlaceholderText() |
1792 | { |
1793 | return m_inputType->updatePlaceholderText(); |
1794 | } |
1795 | |
1796 | bool HTMLInputElement::isEmptyValue() const |
1797 | { |
1798 | return m_inputType->isEmptyValue(); |
1799 | } |
1800 | |
1801 | void HTMLInputElement::maxLengthAttributeChanged(const AtomicString& newValue) |
1802 | { |
1803 | unsigned oldEffectiveMaxLength = effectiveMaxLength(); |
1804 | internalSetMaxLength(parseHTMLNonNegativeInteger(newValue).value_or(-1)); |
1805 | if (oldEffectiveMaxLength != effectiveMaxLength()) |
1806 | updateValueIfNeeded(); |
1807 | |
1808 | // FIXME: Do we really need to do this if the effective maxLength has not changed? |
1809 | invalidateStyleForSubtree(); |
1810 | updateValidity(); |
1811 | } |
1812 | |
1813 | void HTMLInputElement::minLengthAttributeChanged(const AtomicString& newValue) |
1814 | { |
1815 | int oldMinLength = minLength(); |
1816 | internalSetMinLength(parseHTMLNonNegativeInteger(newValue).value_or(-1)); |
1817 | if (oldMinLength != minLength()) |
1818 | updateValueIfNeeded(); |
1819 | |
1820 | // FIXME: Do we really need to do this if the effective minLength has not changed? |
1821 | invalidateStyleForSubtree(); |
1822 | updateValidity(); |
1823 | } |
1824 | |
1825 | void HTMLInputElement::updateValueIfNeeded() |
1826 | { |
1827 | String newValue = sanitizeValue(m_valueIfDirty); |
1828 | ASSERT(!m_valueIfDirty.isNull() || newValue.isNull()); |
1829 | if (newValue != m_valueIfDirty) |
1830 | setValue(newValue); |
1831 | } |
1832 | |
1833 | String HTMLInputElement::defaultToolTip() const |
1834 | { |
1835 | return m_inputType->defaultToolTip(); |
1836 | } |
1837 | |
1838 | bool HTMLInputElement::matchesIndeterminatePseudoClass() const |
1839 | { |
1840 | // For input elements, matchesIndeterminatePseudoClass() |
1841 | // is not equivalent to shouldAppearIndeterminate() because of radio button. |
1842 | // |
1843 | // A group of radio button without any checked button is indeterminate |
1844 | // for the :indeterminate selector. On the other hand, RenderTheme |
1845 | // currently only supports single element being indeterminate. |
1846 | // Because of this, radio is indetermindate for CSS but not for render theme. |
1847 | return m_inputType->matchesIndeterminatePseudoClass(); |
1848 | } |
1849 | |
1850 | bool HTMLInputElement::shouldAppearIndeterminate() const |
1851 | { |
1852 | return m_inputType->shouldAppearIndeterminate(); |
1853 | } |
1854 | |
1855 | #if ENABLE(MEDIA_CAPTURE) |
1856 | MediaCaptureType HTMLInputElement::mediaCaptureType() const |
1857 | { |
1858 | if (!isFileUpload()) |
1859 | return MediaCaptureTypeNone; |
1860 | |
1861 | auto& captureAttribute = attributeWithoutSynchronization(captureAttr); |
1862 | if (captureAttribute.isNull()) |
1863 | return MediaCaptureTypeNone; |
1864 | |
1865 | if (equalLettersIgnoringASCIICase(captureAttribute, "user" )) |
1866 | return MediaCaptureTypeUser; |
1867 | |
1868 | return MediaCaptureTypeEnvironment; |
1869 | } |
1870 | #endif |
1871 | |
1872 | bool HTMLInputElement::isInRequiredRadioButtonGroup() |
1873 | { |
1874 | ASSERT(isRadioButton()); |
1875 | if (RadioButtonGroups* buttons = radioButtonGroups()) |
1876 | return buttons->isInRequiredGroup(*this); |
1877 | return false; |
1878 | } |
1879 | |
1880 | Vector<HTMLInputElement*> HTMLInputElement::radioButtonGroup() const |
1881 | { |
1882 | RadioButtonGroups* buttons = radioButtonGroups(); |
1883 | if (!buttons) |
1884 | return { }; |
1885 | return buttons->groupMembers(*this); |
1886 | } |
1887 | |
1888 | HTMLInputElement* HTMLInputElement::checkedRadioButtonForGroup() const |
1889 | { |
1890 | if (RadioButtonGroups* buttons = radioButtonGroups()) |
1891 | return buttons->checkedButtonForGroup(name()); |
1892 | return nullptr; |
1893 | } |
1894 | |
1895 | RadioButtonGroups* HTMLInputElement::radioButtonGroups() const |
1896 | { |
1897 | if (!isRadioButton()) |
1898 | return nullptr; |
1899 | if (auto* formElement = form()) |
1900 | return &formElement->radioButtonGroups(); |
1901 | if (isConnected()) |
1902 | return &document().formController().radioButtonGroups(); |
1903 | return nullptr; |
1904 | } |
1905 | |
1906 | inline void HTMLInputElement::addToRadioButtonGroup() |
1907 | { |
1908 | if (auto* buttons = radioButtonGroups()) |
1909 | buttons->addButton(*this); |
1910 | } |
1911 | |
1912 | inline void HTMLInputElement::removeFromRadioButtonGroup() |
1913 | { |
1914 | if (auto* buttons = radioButtonGroups()) |
1915 | buttons->removeButton(*this); |
1916 | } |
1917 | |
1918 | unsigned HTMLInputElement::height() const |
1919 | { |
1920 | return m_inputType->height(); |
1921 | } |
1922 | |
1923 | unsigned HTMLInputElement::width() const |
1924 | { |
1925 | return m_inputType->width(); |
1926 | } |
1927 | |
1928 | void HTMLInputElement::setHeight(unsigned height) |
1929 | { |
1930 | setUnsignedIntegralAttribute(heightAttr, height); |
1931 | } |
1932 | |
1933 | void HTMLInputElement::setWidth(unsigned width) |
1934 | { |
1935 | setUnsignedIntegralAttribute(widthAttr, width); |
1936 | } |
1937 | |
1938 | #if ENABLE(DATALIST_ELEMENT) |
1939 | ListAttributeTargetObserver::ListAttributeTargetObserver(const AtomicString& id, HTMLInputElement* element) |
1940 | : IdTargetObserver(element->treeScope().idTargetObserverRegistry(), id) |
1941 | , m_element(element) |
1942 | { |
1943 | } |
1944 | |
1945 | void ListAttributeTargetObserver::idTargetChanged() |
1946 | { |
1947 | m_element->listAttributeTargetChanged(); |
1948 | } |
1949 | #endif |
1950 | |
1951 | ExceptionOr<void> HTMLInputElement::setRangeText(const String& replacement) |
1952 | { |
1953 | if (!m_inputType->supportsSelectionAPI()) |
1954 | return Exception { InvalidStateError }; |
1955 | |
1956 | return HTMLTextFormControlElement::setRangeText(replacement); |
1957 | } |
1958 | |
1959 | ExceptionOr<void> HTMLInputElement::setRangeText(const String& replacement, unsigned start, unsigned end, const String& selectionMode) |
1960 | { |
1961 | if (!m_inputType->supportsSelectionAPI()) |
1962 | return Exception { InvalidStateError }; |
1963 | |
1964 | return HTMLTextFormControlElement::setRangeText(replacement, start, end, selectionMode); |
1965 | } |
1966 | |
1967 | bool HTMLInputElement::shouldTruncateText(const RenderStyle& style) const |
1968 | { |
1969 | if (!isTextField()) |
1970 | return false; |
1971 | return document().focusedElement() != this && style.textOverflow() == TextOverflow::Ellipsis; |
1972 | } |
1973 | |
1974 | ExceptionOr<int> HTMLInputElement::selectionStartForBindings() const |
1975 | { |
1976 | if (!canHaveSelection()) |
1977 | return Exception { TypeError }; |
1978 | |
1979 | return selectionStart(); |
1980 | } |
1981 | |
1982 | ExceptionOr<void> HTMLInputElement::setSelectionStartForBindings(int start) |
1983 | { |
1984 | if (!canHaveSelection()) |
1985 | return Exception { TypeError }; |
1986 | |
1987 | setSelectionStart(start); |
1988 | return { }; |
1989 | } |
1990 | |
1991 | ExceptionOr<int> HTMLInputElement::selectionEndForBindings() const |
1992 | { |
1993 | if (!canHaveSelection()) |
1994 | return Exception { TypeError }; |
1995 | |
1996 | return selectionEnd(); |
1997 | } |
1998 | |
1999 | ExceptionOr<void> HTMLInputElement::setSelectionEndForBindings(int end) |
2000 | { |
2001 | if (!canHaveSelection()) |
2002 | return Exception { TypeError }; |
2003 | |
2004 | setSelectionEnd(end); |
2005 | return { }; |
2006 | } |
2007 | |
2008 | ExceptionOr<String> HTMLInputElement::selectionDirectionForBindings() const |
2009 | { |
2010 | if (!canHaveSelection()) |
2011 | return Exception { TypeError }; |
2012 | |
2013 | return String { selectionDirection() }; |
2014 | } |
2015 | |
2016 | ExceptionOr<void> HTMLInputElement::setSelectionDirectionForBindings(const String& direction) |
2017 | { |
2018 | if (!canHaveSelection()) |
2019 | return Exception { TypeError }; |
2020 | |
2021 | setSelectionDirection(direction); |
2022 | return { }; |
2023 | } |
2024 | |
2025 | ExceptionOr<void> HTMLInputElement::setSelectionRangeForBindings(int start, int end, const String& direction) |
2026 | { |
2027 | if (!canHaveSelection()) |
2028 | return Exception { TypeError }; |
2029 | |
2030 | setSelectionRange(start, end, direction); |
2031 | return { }; |
2032 | } |
2033 | |
2034 | static Ref<CSSLinearGradientValue> autoFillStrongPasswordMaskImage() |
2035 | { |
2036 | CSSGradientColorStop firstStop; |
2037 | firstStop.m_color = CSSValuePool::singleton().createColorValue(Color::black); |
2038 | firstStop.m_position = CSSValuePool::singleton().createValue(50, CSSPrimitiveValue::UnitType::CSS_PERCENTAGE); |
2039 | |
2040 | CSSGradientColorStop secondStop; |
2041 | secondStop.m_color = CSSValuePool::singleton().createColorValue(Color::transparent); |
2042 | secondStop.m_position = CSSValuePool::singleton().createValue(100, CSSPrimitiveValue::UnitType::CSS_PERCENTAGE); |
2043 | |
2044 | auto gradient = CSSLinearGradientValue::create(CSSGradientRepeat::NonRepeating, CSSGradientType::CSSLinearGradient); |
2045 | gradient->setAngle(CSSValuePool::singleton().createValue(90, CSSPrimitiveValue::UnitType::CSS_DEG)); |
2046 | gradient->addStop(firstStop); |
2047 | gradient->addStop(secondStop); |
2048 | return gradient; |
2049 | } |
2050 | |
2051 | RenderStyle HTMLInputElement::createInnerTextStyle(const RenderStyle& style) |
2052 | { |
2053 | auto textBlockStyle = RenderStyle::create(); |
2054 | textBlockStyle.inheritFrom(style); |
2055 | adjustInnerTextStyle(style, textBlockStyle); |
2056 | |
2057 | textBlockStyle.setWhiteSpace(WhiteSpace::Pre); |
2058 | textBlockStyle.setOverflowWrap(OverflowWrap::Normal); |
2059 | textBlockStyle.setOverflowX(Overflow::Hidden); |
2060 | textBlockStyle.setOverflowY(Overflow::Hidden); |
2061 | textBlockStyle.setTextOverflow(shouldTruncateText(style) ? TextOverflow::Ellipsis : TextOverflow::Clip); |
2062 | |
2063 | textBlockStyle.setDisplay(DisplayType::Block); |
2064 | |
2065 | if (hasAutoFillStrongPasswordButton() && !isDisabledOrReadOnly()) { |
2066 | textBlockStyle.setDisplay(DisplayType::InlineBlock); |
2067 | textBlockStyle.setMaxWidth(Length { 100, Percent }); |
2068 | textBlockStyle.setColor({ 0.0f, 0.0f, 0.0f, 0.6f }); |
2069 | textBlockStyle.setTextOverflow(TextOverflow::Clip); |
2070 | textBlockStyle.setMaskImage(styleResolver().styleImage(autoFillStrongPasswordMaskImage())); |
2071 | // A stacking context is needed for the mask. |
2072 | if (textBlockStyle.hasAutoZIndex()) |
2073 | textBlockStyle.setZIndex(0); |
2074 | } |
2075 | |
2076 | // Do not allow line-height to be smaller than our default. |
2077 | if (textBlockStyle.fontMetrics().lineSpacing() > style.computedLineHeight()) |
2078 | textBlockStyle.setLineHeight(RenderStyle::initialLineHeight()); |
2079 | |
2080 | return textBlockStyle; |
2081 | } |
2082 | |
2083 | #if ENABLE(DATE_AND_TIME_INPUT_TYPES) |
2084 | bool HTMLInputElement::setupDateTimeChooserParameters(DateTimeChooserParameters& parameters) |
2085 | { |
2086 | if (!document().view()) |
2087 | return false; |
2088 | |
2089 | parameters.type = type(); |
2090 | parameters.minimum = minimum(); |
2091 | parameters.maximum = maximum(); |
2092 | parameters.required = isRequired(); |
2093 | |
2094 | if (!document().settings().langAttributeAwareFormControlUIEnabled()) |
2095 | parameters.locale = defaultLanguage(); |
2096 | else { |
2097 | AtomicString computedLocale = computeInheritedLanguage(); |
2098 | parameters.locale = computedLocale.isEmpty() ? AtomicString(defaultLanguage()) : computedLocale; |
2099 | } |
2100 | |
2101 | StepRange stepRange = createStepRange(RejectAny); |
2102 | if (stepRange.hasStep()) { |
2103 | parameters.step = stepRange.step().toDouble(); |
2104 | parameters.stepBase = stepRange.stepBase().toDouble(); |
2105 | } else { |
2106 | parameters.step = 1.0; |
2107 | parameters.stepBase = 0; |
2108 | } |
2109 | |
2110 | if (RenderElement* renderer = this->renderer()) |
2111 | parameters.anchorRectInRootView = document().view()->contentsToRootView(renderer->absoluteBoundingBoxRect()); |
2112 | else |
2113 | parameters.anchorRectInRootView = IntRect(); |
2114 | parameters.currentValue = value(); |
2115 | parameters.isAnchorElementRTL = computedStyle()->direction() == TextDirection::RTL; |
2116 | #if ENABLE(DATALIST_ELEMENT) |
2117 | if (auto dataList = this->dataList()) { |
2118 | Ref<HTMLCollection> options = dataList->options(); |
2119 | for (unsigned i = 0; RefPtr<HTMLOptionElement> option = downcast<HTMLOptionElement>(options->item(i)); ++i) { |
2120 | if (!isValidValue(option->value())) |
2121 | continue; |
2122 | parameters.suggestionValues.append(sanitizeValue(option->value())); |
2123 | parameters.localizedSuggestionValues.append(localizeValue(option->value())); |
2124 | parameters.suggestionLabels.append(option->value() == option->label() ? String() : option->label()); |
2125 | } |
2126 | } |
2127 | #endif |
2128 | return true; |
2129 | } |
2130 | #endif |
2131 | |
2132 | void HTMLInputElement::capsLockStateMayHaveChanged() |
2133 | { |
2134 | m_inputType->capsLockStateMayHaveChanged(); |
2135 | } |
2136 | |
2137 | } // namespace |
2138 | |