1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 * Copyright (C) 2011-2018 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "TextFieldInputType.h"
34
35#include "BeforeTextInsertedEvent.h"
36#include "Chrome.h"
37#include "ChromeClient.h"
38#include "DOMFormData.h"
39#include "Editor.h"
40#include "EventNames.h"
41#include "Frame.h"
42#include "FrameSelection.h"
43#include "HTMLInputElement.h"
44#include "HTMLNames.h"
45#include "HTMLParserIdioms.h"
46#include "KeyboardEvent.h"
47#include "LocalizedStrings.h"
48#include "NodeRenderStyle.h"
49#include "Page.h"
50#include "PlatformKeyboardEvent.h"
51#include "RenderLayer.h"
52#include "RenderTextControlSingleLine.h"
53#include "RenderTheme.h"
54#include "RuntimeEnabledFeatures.h"
55#include "ShadowRoot.h"
56#include "TextControlInnerElements.h"
57#include "TextEvent.h"
58#include "TextIterator.h"
59#include "TextNodeTraversal.h"
60#include "UserTypingGestureIndicator.h"
61#include "WheelEvent.h"
62
63#if ENABLE(DATALIST_ELEMENT)
64#include "HTMLDataListElement.h"
65#include "HTMLOptionElement.h"
66#endif
67
68namespace WebCore {
69
70using namespace HTMLNames;
71
72TextFieldInputType::TextFieldInputType(HTMLInputElement& element)
73 : InputType(element)
74{
75}
76
77TextFieldInputType::~TextFieldInputType()
78{
79 if (m_innerSpinButton)
80 m_innerSpinButton->removeSpinButtonOwner();
81#if ENABLE(DATALIST_ELEMENT)
82 closeSuggestions();
83#endif
84}
85
86bool TextFieldInputType::isKeyboardFocusable(KeyboardEvent*) const
87{
88 ASSERT(element());
89#if PLATFORM(IOS_FAMILY)
90 if (element()->isReadOnly())
91 return false;
92#endif
93 return element()->isTextFormControlFocusable();
94}
95
96bool TextFieldInputType::isMouseFocusable() const
97{
98 ASSERT(element());
99 return element()->isTextFormControlFocusable();
100}
101
102bool TextFieldInputType::isTextField() const
103{
104 return true;
105}
106
107bool TextFieldInputType::isEmptyValue() const
108{
109 auto innerText = innerTextElement();
110 ASSERT(innerText);
111
112 for (Text* text = TextNodeTraversal::firstWithin(*innerText); text; text = TextNodeTraversal::next(*text, innerText.get())) {
113 if (text->length())
114 return false;
115 }
116 return true;
117}
118
119bool TextFieldInputType::valueMissing(const String& value) const
120{
121 ASSERT(element());
122 return element()->isRequired() && value.isEmpty();
123}
124
125void TextFieldInputType::setValue(const String& sanitizedValue, bool valueChanged, TextFieldEventBehavior eventBehavior)
126{
127 ASSERT(element());
128
129 // Grab this input element to keep reference even if JS event handler
130 // changes input type.
131 Ref<HTMLInputElement> input(*element());
132
133 // We don't ask InputType::setValue to dispatch events because
134 // TextFieldInputType dispatches events different way from InputType.
135 InputType::setValue(sanitizedValue, valueChanged, DispatchNoEvent);
136
137 if (valueChanged)
138 updateInnerTextValue();
139
140 unsigned max = visibleValue().length();
141 if (input->focused())
142 input->setSelectionRange(max, max);
143 else
144 input->cacheSelectionInResponseToSetValue(max);
145
146 if (!valueChanged)
147 return;
148
149 switch (eventBehavior) {
150 case DispatchChangeEvent:
151 // If the user is still editing this field, dispatch an input event rather than a change event.
152 // The change event will be dispatched when editing finishes.
153 if (input->focused())
154 input->dispatchFormControlInputEvent();
155 else
156 input->dispatchFormControlChangeEvent();
157 break;
158
159 case DispatchInputAndChangeEvent: {
160 input->dispatchFormControlInputEvent();
161 input->dispatchFormControlChangeEvent();
162 break;
163 }
164
165 case DispatchNoEvent:
166 break;
167 }
168
169 // FIXME: Why do we do this when eventBehavior == DispatchNoEvent
170 if (!input->focused() || eventBehavior == DispatchNoEvent)
171 input->setTextAsOfLastFormControlChangeEvent(sanitizedValue);
172
173 if (UserTypingGestureIndicator::processingUserTypingGesture())
174 didSetValueByUserEdit();
175}
176
177#if ENABLE(DATALIST_ELEMENT)
178void TextFieldInputType::handleClickEvent(MouseEvent&)
179{
180 if (element()->focused() && element()->list())
181 displaySuggestions(DataListSuggestionActivationType::ControlClicked);
182}
183#endif
184
185auto TextFieldInputType::handleKeydownEvent(KeyboardEvent& event) -> ShouldCallBaseEventHandler
186{
187 ASSERT(element());
188 if (!element()->focused())
189 return ShouldCallBaseEventHandler::Yes;
190#if ENABLE(DATALIST_ELEMENT)
191 const String& key = event.keyIdentifier();
192 if (m_suggestionPicker && (key == "Enter" || key == "Up" || key == "Down")) {
193 m_suggestionPicker->handleKeydownWithIdentifier(key);
194 event.setDefaultHandled();
195 }
196#endif
197 RefPtr<Frame> frame = element()->document().frame();
198 if (frame && frame->editor().doTextFieldCommandFromEvent(element(), &event))
199 event.setDefaultHandled();
200 return ShouldCallBaseEventHandler::Yes;
201}
202
203void TextFieldInputType::handleKeydownEventForSpinButton(KeyboardEvent& event)
204{
205 ASSERT(element());
206 if (element()->isDisabledOrReadOnly())
207 return;
208#if ENABLE(DATALIST_ELEMENT)
209 if (m_suggestionPicker)
210 return;
211#endif
212 const String& key = event.keyIdentifier();
213 if (key == "Up")
214 spinButtonStepUp();
215 else if (key == "Down")
216 spinButtonStepDown();
217 else
218 return;
219 event.setDefaultHandled();
220}
221
222void TextFieldInputType::forwardEvent(Event& event)
223{
224 if (m_innerSpinButton) {
225 m_innerSpinButton->forwardEvent(event);
226 if (event.defaultHandled())
227 return;
228 }
229
230 bool isFocusEvent = event.type() == eventNames().focusEvent;
231 bool isBlurEvent = event.type() == eventNames().blurEvent;
232 if (isFocusEvent || isBlurEvent)
233 capsLockStateMayHaveChanged();
234 if (event.isMouseEvent() || isFocusEvent || isBlurEvent) {
235 ASSERT(element());
236 element()->forwardEvent(event);
237 }
238}
239
240void TextFieldInputType::elementDidBlur()
241{
242 ASSERT(element());
243 auto* renderer = element()->renderer();
244 if (!renderer)
245 return;
246
247 auto* innerTextRenderer = innerTextElement()->renderer();
248 if (!innerTextRenderer)
249 return;
250
251 auto* innerLayer = innerTextRenderer->layer();
252 if (!innerLayer)
253 return;
254
255 bool isLeftToRightDirection = downcast<RenderTextControlSingleLine>(*renderer).style().isLeftToRightDirection();
256 ScrollOffset scrollOffset(isLeftToRightDirection ? 0 : innerLayer->scrollWidth(), 0);
257 innerLayer->scrollToOffset(scrollOffset);
258
259#if ENABLE(DATALIST_ELEMENT)
260 closeSuggestions();
261#endif
262}
263
264void TextFieldInputType::handleFocusEvent(Node* oldFocusedNode, FocusDirection)
265{
266 ASSERT(element());
267 ASSERT_UNUSED(oldFocusedNode, oldFocusedNode != element());
268 if (RefPtr<Frame> frame = element()->document().frame()) {
269 frame->editor().textFieldDidBeginEditing(element());
270#if ENABLE(DATALIST_ELEMENT) && PLATFORM(IOS_FAMILY)
271 if (element()->list() && m_dataListDropdownIndicator)
272 m_dataListDropdownIndicator->setInlineStyleProperty(CSSPropertyDisplay, suggestions().size() ? CSSValueBlock : CSSValueNone, true);
273#endif
274 }
275}
276
277void TextFieldInputType::handleBlurEvent()
278{
279 InputType::handleBlurEvent();
280 ASSERT(element());
281 element()->endEditing();
282#if ENABLE(DATALIST_ELEMENT) && PLATFORM(IOS_FAMILY)
283 if (element()->list() && m_dataListDropdownIndicator)
284 m_dataListDropdownIndicator->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true);
285#endif
286}
287
288bool TextFieldInputType::shouldSubmitImplicitly(Event& event)
289{
290 return (event.type() == eventNames().textInputEvent && is<TextEvent>(event) && downcast<TextEvent>(event).data() == "\n")
291 || InputType::shouldSubmitImplicitly(event);
292}
293
294RenderPtr<RenderElement> TextFieldInputType::createInputRenderer(RenderStyle&& style)
295{
296 ASSERT(element());
297 return createRenderer<RenderTextControlSingleLine>(*element(), WTFMove(style));
298}
299
300bool TextFieldInputType::needsContainer() const
301{
302 return false;
303}
304
305bool TextFieldInputType::shouldHaveSpinButton() const
306{
307 ASSERT(element());
308 return RenderTheme::singleton().shouldHaveSpinButton(*element());
309}
310
311bool TextFieldInputType::shouldHaveCapsLockIndicator() const
312{
313 ASSERT(element());
314 return RenderTheme::singleton().shouldHaveCapsLockIndicator(*element());
315}
316
317void TextFieldInputType::createShadowSubtree()
318{
319 ASSERT(element());
320 ASSERT(element()->shadowRoot());
321
322 ASSERT(!m_innerText);
323 ASSERT(!m_innerBlock);
324 ASSERT(!m_innerSpinButton);
325 ASSERT(!m_capsLockIndicator);
326 ASSERT(!m_autoFillButton);
327
328 Document& document = element()->document();
329 bool shouldHaveSpinButton = this->shouldHaveSpinButton();
330 bool shouldHaveCapsLockIndicator = this->shouldHaveCapsLockIndicator();
331 bool createsContainer = shouldHaveSpinButton || shouldHaveCapsLockIndicator || needsContainer();
332
333 m_innerText = TextControlInnerTextElement::create(document);
334
335 if (!createsContainer) {
336 element()->userAgentShadowRoot()->appendChild(*m_innerText);
337 updatePlaceholderText();
338 return;
339 }
340
341 createContainer();
342 updatePlaceholderText();
343
344 if (shouldHaveSpinButton) {
345 m_innerSpinButton = SpinButtonElement::create(document, *this);
346 m_container->appendChild(*m_innerSpinButton);
347 }
348
349 if (shouldHaveCapsLockIndicator) {
350 m_capsLockIndicator = HTMLDivElement::create(document);
351 m_capsLockIndicator->setPseudo(AtomicString("-webkit-caps-lock-indicator", AtomicString::ConstructFromLiteral));
352
353 bool shouldDrawCapsLockIndicator = this->shouldDrawCapsLockIndicator();
354 m_capsLockIndicator->setInlineStyleProperty(CSSPropertyDisplay, shouldDrawCapsLockIndicator ? CSSValueBlock : CSSValueNone, true);
355
356 m_container->appendChild(*m_capsLockIndicator);
357 }
358 updateAutoFillButton();
359}
360
361HTMLElement* TextFieldInputType::containerElement() const
362{
363 return m_container.get();
364}
365
366HTMLElement* TextFieldInputType::innerBlockElement() const
367{
368 return m_innerBlock.get();
369}
370
371RefPtr<TextControlInnerTextElement> TextFieldInputType::innerTextElement() const
372{
373 ASSERT(m_innerText);
374 return m_innerText;
375}
376
377HTMLElement* TextFieldInputType::innerSpinButtonElement() const
378{
379 return m_innerSpinButton.get();
380}
381
382HTMLElement* TextFieldInputType::capsLockIndicatorElement() const
383{
384 return m_capsLockIndicator.get();
385}
386
387HTMLElement* TextFieldInputType::autoFillButtonElement() const
388{
389 return m_autoFillButton.get();
390}
391
392HTMLElement* TextFieldInputType::placeholderElement() const
393{
394 return m_placeholder.get();
395}
396
397void TextFieldInputType::destroyShadowSubtree()
398{
399 InputType::destroyShadowSubtree();
400 m_innerText = nullptr;
401 m_placeholder = nullptr;
402 m_innerBlock = nullptr;
403 if (m_innerSpinButton)
404 m_innerSpinButton->removeSpinButtonOwner();
405 m_innerSpinButton = nullptr;
406 m_capsLockIndicator = nullptr;
407 m_autoFillButton = nullptr;
408#if ENABLE(DATALIST)
409 m_dataListDropdownIndicator = nullptr;
410#endif
411 m_container = nullptr;
412}
413
414void TextFieldInputType::attributeChanged(const QualifiedName& name)
415{
416 if (name == valueAttr || name == placeholderAttr) {
417 if (element())
418 updateInnerTextValue();
419 }
420 InputType::attributeChanged(name);
421}
422
423void TextFieldInputType::disabledStateChanged()
424{
425 if (m_innerSpinButton)
426 m_innerSpinButton->releaseCapture();
427 capsLockStateMayHaveChanged();
428 updateAutoFillButton();
429}
430
431void TextFieldInputType::readOnlyStateChanged()
432{
433 if (m_innerSpinButton)
434 m_innerSpinButton->releaseCapture();
435 capsLockStateMayHaveChanged();
436 updateAutoFillButton();
437}
438
439bool TextFieldInputType::supportsReadOnly() const
440{
441 return true;
442}
443
444bool TextFieldInputType::shouldUseInputMethod() const
445{
446 return true;
447}
448
449#if ENABLE(DATALIST_ELEMENT)
450void TextFieldInputType::createDataListDropdownIndicator()
451{
452 ASSERT(!m_dataListDropdownIndicator);
453 if (!m_container)
454 createContainer();
455 m_dataListDropdownIndicator = DataListButtonElement::create(element()->document(), *this);
456 m_dataListDropdownIndicator->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true);
457 m_container->appendChild(*m_dataListDropdownIndicator);
458}
459#endif
460
461// FIXME: The name of this function doesn't make clear the two jobs it does:
462// 1) Limits the string to a particular number of grapheme clusters.
463// 2) Truncates the string at the first character which is a control character other than tab.
464// FIXME: TextFieldInputType::sanitizeValue doesn't need a limit on grapheme clusters. A limit on code units would do.
465// FIXME: Where does the "truncate at first control character" rule come from?
466static String limitLength(const String& string, unsigned maxNumGraphemeClusters)
467{
468 StringView stringView { string };
469 unsigned firstNonTabControlCharacterIndex = stringView.find([] (UChar character) {
470 return character < ' ' && character != '\t';
471 });
472 unsigned limitedLength;
473 if (stringView.is8Bit())
474 limitedLength = std::min(firstNonTabControlCharacterIndex, maxNumGraphemeClusters);
475 else
476 limitedLength = numCodeUnitsInGraphemeClusters(stringView.substring(0, firstNonTabControlCharacterIndex), maxNumGraphemeClusters);
477 return string.left(limitedLength);
478}
479
480static String autoFillButtonTypeToAccessibilityLabel(AutoFillButtonType autoFillButtonType)
481{
482 switch (autoFillButtonType) {
483 case AutoFillButtonType::Contacts:
484 return AXAutoFillContactsLabel();
485 case AutoFillButtonType::Credentials:
486 return AXAutoFillCredentialsLabel();
487 case AutoFillButtonType::StrongPassword:
488 return AXAutoFillStrongPasswordLabel();
489 case AutoFillButtonType::CreditCard:
490 return AXAutoFillCreditCardLabel();
491 case AutoFillButtonType::None:
492 ASSERT_NOT_REACHED();
493 return { };
494 }
495 ASSERT_NOT_REACHED();
496 return { };
497}
498
499static String autoFillButtonTypeToAutoFillButtonText(AutoFillButtonType autoFillButtonType)
500{
501 switch (autoFillButtonType) {
502 case AutoFillButtonType::Contacts:
503 case AutoFillButtonType::Credentials:
504 case AutoFillButtonType::CreditCard:
505 return emptyString();
506 case AutoFillButtonType::StrongPassword:
507 return autoFillStrongPasswordLabel();
508 case AutoFillButtonType::None:
509 ASSERT_NOT_REACHED();
510 return { };
511 }
512 ASSERT_NOT_REACHED();
513 return { };
514}
515
516static AtomicString autoFillButtonTypeToAutoFillButtonPseudoClassName(AutoFillButtonType autoFillButtonType)
517{
518 switch (autoFillButtonType) {
519 case AutoFillButtonType::Contacts:
520 return { "-webkit-contacts-auto-fill-button", AtomicString::ConstructFromLiteral };
521 case AutoFillButtonType::Credentials:
522 return { "-webkit-credentials-auto-fill-button", AtomicString::ConstructFromLiteral };
523 case AutoFillButtonType::StrongPassword:
524 return { "-webkit-strong-password-auto-fill-button", AtomicString::ConstructFromLiteral };
525 case AutoFillButtonType::CreditCard:
526 return { "-webkit-credit-card-auto-fill-button", AtomicString::ConstructFromLiteral };
527 case AutoFillButtonType::None:
528 ASSERT_NOT_REACHED();
529 return emptyAtom();
530 }
531 ASSERT_NOT_REACHED();
532 return { };
533}
534
535static bool isAutoFillButtonTypeChanged(const AtomicString& attribute, AutoFillButtonType autoFillButtonType)
536{
537 if (attribute == "-webkit-contacts-auto-fill-button" && autoFillButtonType != AutoFillButtonType::Contacts)
538 return true;
539 if (attribute == "-webkit-credentials-auto-fill-button" && autoFillButtonType != AutoFillButtonType::Credentials)
540 return true;
541 if (attribute == "-webkit-strong-password-auto-fill-button" && autoFillButtonType != AutoFillButtonType::StrongPassword)
542 return true;
543 if (attribute == "-webkit-credit-card-auto-fill-button" && autoFillButtonType != AutoFillButtonType::CreditCard)
544 return true;
545 return false;
546}
547
548String TextFieldInputType::sanitizeValue(const String& proposedValue) const
549{
550 return limitLength(proposedValue.removeCharacters(isHTMLLineBreak), HTMLInputElement::maxEffectiveLength);
551}
552
553void TextFieldInputType::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent& event)
554{
555 ASSERT(element());
556 // Make sure that the text to be inserted will not violate the maxLength.
557
558 // We use RenderTextControlSingleLine::text() instead of InputElement::value()
559 // because they can be mismatched by sanitizeValue() in
560 // HTMLInputElement::subtreeHasChanged() in some cases.
561 String innerText = element()->innerTextValue();
562 unsigned oldLength = numGraphemeClusters(innerText);
563
564 // selectionLength represents the selection length of this text field to be
565 // removed by this insertion.
566 // If the text field has no focus, we don't need to take account of the
567 // selection length. The selection is the source of text drag-and-drop in
568 // that case, and nothing in the text field will be removed.
569 unsigned selectionLength = 0;
570 if (element()->focused()) {
571 ASSERT(enclosingTextFormControl(element()->document().frame()->selection().selection().start()) == element());
572 int selectionStart = element()->selectionStart();
573 ASSERT(selectionStart <= element()->selectionEnd());
574 int selectionCodeUnitCount = element()->selectionEnd() - selectionStart;
575 selectionLength = selectionCodeUnitCount ? numGraphemeClusters(StringView(innerText).substring(selectionStart, selectionCodeUnitCount)) : 0;
576 }
577 ASSERT(oldLength >= selectionLength);
578
579 // Selected characters will be removed by the next text event.
580 unsigned baseLength = oldLength - selectionLength;
581 unsigned maxLength = isTextType() ? element()->effectiveMaxLength() : HTMLInputElement::maxEffectiveLength;
582 unsigned appendableLength = maxLength > baseLength ? maxLength - baseLength : 0;
583
584 // Truncate the inserted text to avoid violating the maxLength and other constraints.
585 String eventText = event.text();
586 unsigned textLength = eventText.length();
587 while (textLength > 0 && isHTMLLineBreak(eventText[textLength - 1]))
588 textLength--;
589 eventText.truncate(textLength);
590 eventText.replace("\r\n", " ");
591 eventText.replace('\r', ' ');
592 eventText.replace('\n', ' ');
593 event.setText(limitLength(eventText, appendableLength));
594}
595
596bool TextFieldInputType::shouldRespectListAttribute()
597{
598#if ENABLE(DATALIST_ELEMENT)
599 return RuntimeEnabledFeatures::sharedFeatures().dataListElementEnabled();
600#else
601 return InputType::themeSupportsDataListUI(this);
602#endif
603}
604
605void TextFieldInputType::updatePlaceholderText()
606{
607 if (!supportsPlaceholder())
608 return;
609 ASSERT(element());
610 String placeholderText = element()->strippedPlaceholder();
611 if (placeholderText.isEmpty()) {
612 if (m_placeholder) {
613 m_placeholder->parentNode()->removeChild(*m_placeholder);
614 m_placeholder = nullptr;
615 }
616 return;
617 }
618 if (!m_placeholder) {
619 m_placeholder = TextControlPlaceholderElement::create(element()->document());
620 element()->userAgentShadowRoot()->insertBefore(*m_placeholder, m_container ? m_container.get() : innerTextElement().get());
621 }
622 m_placeholder->setInnerText(placeholderText);
623}
624
625bool TextFieldInputType::appendFormData(DOMFormData& formData, bool multipart) const
626{
627 InputType::appendFormData(formData, multipart);
628 ASSERT(element());
629 auto& dirnameAttrValue = element()->attributeWithoutSynchronization(dirnameAttr);
630 if (!dirnameAttrValue.isNull())
631 formData.append(dirnameAttrValue, element()->directionForFormData());
632 return true;
633}
634
635String TextFieldInputType::convertFromVisibleValue(const String& visibleValue) const
636{
637 return visibleValue;
638}
639
640void TextFieldInputType::subtreeHasChanged()
641{
642 ASSERT(element());
643 element()->setChangedSinceLastFormControlChangeEvent(true);
644
645 // We don't need to call sanitizeUserInputValue() function here because
646 // HTMLInputElement::handleBeforeTextInsertedEvent() has already called
647 // sanitizeUserInputValue().
648 // ---
649 // sanitizeValue() is needed because IME input doesn't dispatch BeforeTextInsertedEvent.
650 // ---
651 // Input types that support the selection API do *not* sanitize their
652 // user input in order to retain parity between what's in the model and
653 // what's on the screen. Otherwise, we retain the sanitization process for
654 // backward compatibility. https://bugs.webkit.org/show_bug.cgi?id=150346
655 String innerText = convertFromVisibleValue(element()->innerTextValue());
656 if (!supportsSelectionAPI())
657 innerText = sanitizeValue(innerText);
658 element()->setValueFromRenderer(innerText);
659 element()->updatePlaceholderVisibility();
660 // Recalc for :invalid change.
661 element()->invalidateStyleForSubtree();
662
663 didSetValueByUserEdit();
664}
665
666void TextFieldInputType::didSetValueByUserEdit()
667{
668 ASSERT(element());
669 if (!element()->focused())
670 return;
671 if (RefPtr<Frame> frame = element()->document().frame())
672 frame->editor().textDidChangeInTextField(element());
673#if ENABLE(DATALIST_ELEMENT)
674#if PLATFORM(IOS_FAMILY)
675 if (element()->list() && m_dataListDropdownIndicator)
676 m_dataListDropdownIndicator->setInlineStyleProperty(CSSPropertyDisplay, suggestions().size() ? CSSValueBlock : CSSValueNone, true);
677#endif
678 if (element()->list())
679 displaySuggestions(DataListSuggestionActivationType::TextChanged);
680#endif
681}
682
683void TextFieldInputType::spinButtonStepDown()
684{
685 stepUpFromRenderer(-1);
686}
687
688void TextFieldInputType::spinButtonStepUp()
689{
690 stepUpFromRenderer(1);
691}
692
693void TextFieldInputType::updateInnerTextValue()
694{
695 ASSERT(element());
696 if (!element()->formControlValueMatchesRenderer()) {
697 // Update the renderer value if the formControlValueMatchesRenderer() flag is false.
698 // It protects an unacceptable renderer value from being overwritten with the DOM value.
699 element()->setInnerTextValue(visibleValue());
700 element()->updatePlaceholderVisibility();
701 }
702}
703
704void TextFieldInputType::focusAndSelectSpinButtonOwner()
705{
706 ASSERT(element());
707 Ref<HTMLInputElement> input(*element());
708 input->focus();
709 input->select();
710}
711
712bool TextFieldInputType::shouldSpinButtonRespondToMouseEvents()
713{
714 ASSERT(element());
715 return !element()->isDisabledOrReadOnly();
716}
717
718bool TextFieldInputType::shouldSpinButtonRespondToWheelEvents()
719{
720 ASSERT(element());
721 return shouldSpinButtonRespondToMouseEvents() && element()->focused();
722}
723
724bool TextFieldInputType::shouldDrawCapsLockIndicator() const
725{
726 ASSERT(element());
727 if (element()->document().focusedElement() != element())
728 return false;
729
730 if (element()->isDisabledOrReadOnly())
731 return false;
732
733 if (element()->hasAutoFillStrongPasswordButton())
734 return false;
735
736 RefPtr<Frame> frame = element()->document().frame();
737 if (!frame)
738 return false;
739
740 if (!frame->selection().isFocusedAndActive())
741 return false;
742
743 return PlatformKeyboardEvent::currentCapsLockState();
744}
745
746void TextFieldInputType::capsLockStateMayHaveChanged()
747{
748 if (!m_capsLockIndicator)
749 return;
750
751 bool shouldDrawCapsLockIndicator = this->shouldDrawCapsLockIndicator();
752 m_capsLockIndicator->setInlineStyleProperty(CSSPropertyDisplay, shouldDrawCapsLockIndicator ? CSSValueBlock : CSSValueNone, true);
753}
754
755bool TextFieldInputType::shouldDrawAutoFillButton() const
756{
757 ASSERT(element());
758 return !element()->isDisabledOrReadOnly() && element()->autoFillButtonType() != AutoFillButtonType::None;
759}
760
761void TextFieldInputType::autoFillButtonElementWasClicked()
762{
763 ASSERT(element());
764 Page* page = element()->document().page();
765 if (!page)
766 return;
767
768 page->chrome().client().handleAutoFillButtonClick(*element());
769}
770
771void TextFieldInputType::createContainer()
772{
773 ASSERT(!m_container);
774 ASSERT(element());
775
776 m_container = TextControlInnerContainer::create(element()->document());
777 m_container->setPseudo(AtomicString("-webkit-textfield-decoration-container", AtomicString::ConstructFromLiteral));
778
779 m_innerBlock = TextControlInnerElement::create(element()->document());
780 m_innerBlock->appendChild(*m_innerText);
781 m_container->appendChild(*m_innerBlock);
782
783 element()->userAgentShadowRoot()->appendChild(*m_container);
784}
785
786void TextFieldInputType::createAutoFillButton(AutoFillButtonType autoFillButtonType)
787{
788 ASSERT(!m_autoFillButton);
789
790 if (autoFillButtonType == AutoFillButtonType::None)
791 return;
792
793 ASSERT(element());
794 m_autoFillButton = AutoFillButtonElement::create(element()->document(), *this);
795 m_autoFillButton->setPseudo(autoFillButtonTypeToAutoFillButtonPseudoClassName(autoFillButtonType));
796 m_autoFillButton->setAttributeWithoutSynchronization(roleAttr, AtomicString("button", AtomicString::ConstructFromLiteral));
797 m_autoFillButton->setAttributeWithoutSynchronization(aria_labelAttr, autoFillButtonTypeToAccessibilityLabel(autoFillButtonType));
798 m_autoFillButton->setTextContent(autoFillButtonTypeToAutoFillButtonText(autoFillButtonType));
799 m_container->appendChild(*m_autoFillButton);
800}
801
802void TextFieldInputType::updateAutoFillButton()
803{
804 capsLockStateMayHaveChanged();
805
806 if (shouldDrawAutoFillButton()) {
807 if (!m_container)
808 createContainer();
809
810 ASSERT(element());
811 AutoFillButtonType autoFillButtonType = element()->autoFillButtonType();
812 if (!m_autoFillButton)
813 createAutoFillButton(autoFillButtonType);
814
815 const AtomicString& attribute = m_autoFillButton->attributeWithoutSynchronization(pseudoAttr);
816 bool shouldUpdateAutoFillButtonType = isAutoFillButtonTypeChanged(attribute, autoFillButtonType);
817 if (shouldUpdateAutoFillButtonType) {
818 m_autoFillButton->setPseudo(autoFillButtonTypeToAutoFillButtonPseudoClassName(autoFillButtonType));
819 m_autoFillButton->setAttributeWithoutSynchronization(aria_labelAttr, autoFillButtonTypeToAccessibilityLabel(autoFillButtonType));
820 m_autoFillButton->setTextContent(autoFillButtonTypeToAutoFillButtonText(autoFillButtonType));
821 }
822 m_autoFillButton->setInlineStyleProperty(CSSPropertyDisplay, CSSValueBlock, true);
823 return;
824 }
825
826 if (m_autoFillButton)
827 m_autoFillButton->setInlineStyleProperty(CSSPropertyDisplay, CSSValueNone, true);
828}
829
830#if ENABLE(DATALIST_ELEMENT)
831
832void TextFieldInputType::listAttributeTargetChanged()
833{
834 m_cachedSuggestions = std::make_pair(String(), Vector<String>());
835
836 if (!m_dataListDropdownIndicator)
837 createDataListDropdownIndicator();
838
839#if !PLATFORM(IOS_FAMILY)
840 m_dataListDropdownIndicator->setInlineStyleProperty(CSSPropertyDisplay, element()->list() ? CSSValueBlock : CSSValueNone, true);
841#endif
842}
843
844HTMLElement* TextFieldInputType::dataListButtonElement() const
845{
846 return m_dataListDropdownIndicator.get();
847}
848
849void TextFieldInputType::dataListButtonElementWasClicked()
850{
851 if (element()->list())
852 displaySuggestions(DataListSuggestionActivationType::IndicatorClicked);
853}
854
855IntRect TextFieldInputType::elementRectInRootViewCoordinates() const
856{
857 if (!element()->renderer())
858 return IntRect();
859 return element()->document().view()->contentsToRootView(element()->renderer()->absoluteBoundingBoxRect());
860}
861
862Vector<String> TextFieldInputType::suggestions()
863{
864 Vector<String> suggestions;
865 Vector<String> matchesContainingValue;
866
867 String elementValue = element()->value();
868
869 if (!m_cachedSuggestions.first.isNull() && equalIgnoringASCIICase(m_cachedSuggestions.first, elementValue))
870 return m_cachedSuggestions.second;
871
872 if (auto dataList = element()->dataList()) {
873 Ref<HTMLCollection> options = dataList->options();
874 for (unsigned i = 0; auto* option = downcast<HTMLOptionElement>(options->item(i)); ++i) {
875 if (!element()->isValidValue(option->value()))
876 continue;
877
878 String value = sanitizeValue(option->value());
879 if (elementValue.isEmpty())
880 suggestions.append(value);
881 else if (value.startsWithIgnoringASCIICase(elementValue))
882 suggestions.append(value);
883 else if (value.containsIgnoringASCIICase(elementValue))
884 matchesContainingValue.append(value);
885 }
886 }
887
888 suggestions.appendVector(matchesContainingValue);
889 m_cachedSuggestions = std::make_pair(elementValue, suggestions);
890
891 return suggestions;
892}
893
894void TextFieldInputType::didSelectDataListOption(const String& selectedOption)
895{
896 element()->setValue(selectedOption, DispatchInputAndChangeEvent);
897}
898
899void TextFieldInputType::didCloseSuggestions()
900{
901 m_cachedSuggestions = std::make_pair(String(), Vector<String>());
902 m_suggestionPicker = nullptr;
903 if (element()->renderer())
904 element()->renderer()->repaint();
905}
906
907void TextFieldInputType::displaySuggestions(DataListSuggestionActivationType type)
908{
909 if (element()->isDisabledFormControl() || !element()->renderer())
910 return;
911
912 if (!UserGestureIndicator::processingUserGesture() && type != DataListSuggestionActivationType::TextChanged)
913 return;
914
915 if (!m_suggestionPicker && suggestions().size() > 0)
916 m_suggestionPicker = chrome()->createDataListSuggestionPicker(*this);
917
918 if (!m_suggestionPicker)
919 return;
920
921 m_suggestionPicker->displayWithActivationType(type);
922}
923
924void TextFieldInputType::closeSuggestions()
925{
926 if (m_suggestionPicker)
927 m_suggestionPicker->close();
928}
929
930bool TextFieldInputType::isPresentingAttachedView() const
931{
932 return !!m_suggestionPicker;
933}
934
935#endif
936
937} // namespace WebCore
938