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 | |
68 | namespace WebCore { |
69 | |
70 | using namespace HTMLNames; |
71 | |
72 | TextFieldInputType::TextFieldInputType(HTMLInputElement& element) |
73 | : InputType(element) |
74 | { |
75 | } |
76 | |
77 | TextFieldInputType::~TextFieldInputType() |
78 | { |
79 | if (m_innerSpinButton) |
80 | m_innerSpinButton->removeSpinButtonOwner(); |
81 | #if ENABLE(DATALIST_ELEMENT) |
82 | closeSuggestions(); |
83 | #endif |
84 | } |
85 | |
86 | bool 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 | |
96 | bool TextFieldInputType::isMouseFocusable() const |
97 | { |
98 | ASSERT(element()); |
99 | return element()->isTextFormControlFocusable(); |
100 | } |
101 | |
102 | bool TextFieldInputType::isTextField() const |
103 | { |
104 | return true; |
105 | } |
106 | |
107 | bool 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 | |
119 | bool TextFieldInputType::valueMissing(const String& value) const |
120 | { |
121 | ASSERT(element()); |
122 | return element()->isRequired() && value.isEmpty(); |
123 | } |
124 | |
125 | void 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) |
178 | void TextFieldInputType::handleClickEvent(MouseEvent&) |
179 | { |
180 | if (element()->focused() && element()->list()) |
181 | displaySuggestions(DataListSuggestionActivationType::ControlClicked); |
182 | } |
183 | #endif |
184 | |
185 | auto 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 | |
203 | void 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 | |
222 | void 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 | |
240 | void 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 | |
264 | void 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 | |
277 | void 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 | |
288 | bool 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 | |
294 | RenderPtr<RenderElement> TextFieldInputType::createInputRenderer(RenderStyle&& style) |
295 | { |
296 | ASSERT(element()); |
297 | return createRenderer<RenderTextControlSingleLine>(*element(), WTFMove(style)); |
298 | } |
299 | |
300 | bool TextFieldInputType::needsContainer() const |
301 | { |
302 | return false; |
303 | } |
304 | |
305 | bool TextFieldInputType::shouldHaveSpinButton() const |
306 | { |
307 | ASSERT(element()); |
308 | return RenderTheme::singleton().shouldHaveSpinButton(*element()); |
309 | } |
310 | |
311 | bool TextFieldInputType::shouldHaveCapsLockIndicator() const |
312 | { |
313 | ASSERT(element()); |
314 | return RenderTheme::singleton().shouldHaveCapsLockIndicator(*element()); |
315 | } |
316 | |
317 | void 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 | |
361 | HTMLElement* TextFieldInputType::containerElement() const |
362 | { |
363 | return m_container.get(); |
364 | } |
365 | |
366 | HTMLElement* TextFieldInputType::innerBlockElement() const |
367 | { |
368 | return m_innerBlock.get(); |
369 | } |
370 | |
371 | RefPtr<TextControlInnerTextElement> TextFieldInputType::innerTextElement() const |
372 | { |
373 | ASSERT(m_innerText); |
374 | return m_innerText; |
375 | } |
376 | |
377 | HTMLElement* TextFieldInputType::innerSpinButtonElement() const |
378 | { |
379 | return m_innerSpinButton.get(); |
380 | } |
381 | |
382 | HTMLElement* TextFieldInputType::capsLockIndicatorElement() const |
383 | { |
384 | return m_capsLockIndicator.get(); |
385 | } |
386 | |
387 | HTMLElement* TextFieldInputType::autoFillButtonElement() const |
388 | { |
389 | return m_autoFillButton.get(); |
390 | } |
391 | |
392 | HTMLElement* TextFieldInputType::placeholderElement() const |
393 | { |
394 | return m_placeholder.get(); |
395 | } |
396 | |
397 | void 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 | |
414 | void TextFieldInputType::attributeChanged(const QualifiedName& name) |
415 | { |
416 | if (name == valueAttr || name == placeholderAttr) { |
417 | if (element()) |
418 | updateInnerTextValue(); |
419 | } |
420 | InputType::attributeChanged(name); |
421 | } |
422 | |
423 | void TextFieldInputType::disabledStateChanged() |
424 | { |
425 | if (m_innerSpinButton) |
426 | m_innerSpinButton->releaseCapture(); |
427 | capsLockStateMayHaveChanged(); |
428 | updateAutoFillButton(); |
429 | } |
430 | |
431 | void TextFieldInputType::readOnlyStateChanged() |
432 | { |
433 | if (m_innerSpinButton) |
434 | m_innerSpinButton->releaseCapture(); |
435 | capsLockStateMayHaveChanged(); |
436 | updateAutoFillButton(); |
437 | } |
438 | |
439 | bool TextFieldInputType::supportsReadOnly() const |
440 | { |
441 | return true; |
442 | } |
443 | |
444 | bool TextFieldInputType::shouldUseInputMethod() const |
445 | { |
446 | return true; |
447 | } |
448 | |
449 | #if ENABLE(DATALIST_ELEMENT) |
450 | void 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? |
466 | static 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 | |
480 | static 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 | |
499 | static 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 | |
516 | static 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 | |
535 | static 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 | |
548 | String TextFieldInputType::sanitizeValue(const String& proposedValue) const |
549 | { |
550 | return limitLength(proposedValue.removeCharacters(isHTMLLineBreak), HTMLInputElement::maxEffectiveLength); |
551 | } |
552 | |
553 | void 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 | |
596 | bool TextFieldInputType::shouldRespectListAttribute() |
597 | { |
598 | #if ENABLE(DATALIST_ELEMENT) |
599 | return RuntimeEnabledFeatures::sharedFeatures().dataListElementEnabled(); |
600 | #else |
601 | return InputType::themeSupportsDataListUI(this); |
602 | #endif |
603 | } |
604 | |
605 | void 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 | |
625 | bool 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 | |
635 | String TextFieldInputType::convertFromVisibleValue(const String& visibleValue) const |
636 | { |
637 | return visibleValue; |
638 | } |
639 | |
640 | void 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 | |
666 | void 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 | |
683 | void TextFieldInputType::spinButtonStepDown() |
684 | { |
685 | stepUpFromRenderer(-1); |
686 | } |
687 | |
688 | void TextFieldInputType::spinButtonStepUp() |
689 | { |
690 | stepUpFromRenderer(1); |
691 | } |
692 | |
693 | void 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 | |
704 | void TextFieldInputType::focusAndSelectSpinButtonOwner() |
705 | { |
706 | ASSERT(element()); |
707 | Ref<HTMLInputElement> input(*element()); |
708 | input->focus(); |
709 | input->select(); |
710 | } |
711 | |
712 | bool TextFieldInputType::shouldSpinButtonRespondToMouseEvents() |
713 | { |
714 | ASSERT(element()); |
715 | return !element()->isDisabledOrReadOnly(); |
716 | } |
717 | |
718 | bool TextFieldInputType::shouldSpinButtonRespondToWheelEvents() |
719 | { |
720 | ASSERT(element()); |
721 | return shouldSpinButtonRespondToMouseEvents() && element()->focused(); |
722 | } |
723 | |
724 | bool 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 | |
746 | void 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 | |
755 | bool TextFieldInputType::shouldDrawAutoFillButton() const |
756 | { |
757 | ASSERT(element()); |
758 | return !element()->isDisabledOrReadOnly() && element()->autoFillButtonType() != AutoFillButtonType::None; |
759 | } |
760 | |
761 | void 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 | |
771 | void 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 | |
786 | void 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 | |
802 | void 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 | |
832 | void 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 | |
844 | HTMLElement* TextFieldInputType::dataListButtonElement() const |
845 | { |
846 | return m_dataListDropdownIndicator.get(); |
847 | } |
848 | |
849 | void TextFieldInputType::dataListButtonElementWasClicked() |
850 | { |
851 | if (element()->list()) |
852 | displaySuggestions(DataListSuggestionActivationType::IndicatorClicked); |
853 | } |
854 | |
855 | IntRect TextFieldInputType::elementRectInRootViewCoordinates() const |
856 | { |
857 | if (!element()->renderer()) |
858 | return IntRect(); |
859 | return element()->document().view()->contentsToRootView(element()->renderer()->absoluteBoundingBoxRect()); |
860 | } |
861 | |
862 | Vector<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 | |
894 | void TextFieldInputType::didSelectDataListOption(const String& selectedOption) |
895 | { |
896 | element()->setValue(selectedOption, DispatchInputAndChangeEvent); |
897 | } |
898 | |
899 | void 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 | |
907 | void 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 | |
924 | void TextFieldInputType::closeSuggestions() |
925 | { |
926 | if (m_suggestionPicker) |
927 | m_suggestionPicker->close(); |
928 | } |
929 | |
930 | bool TextFieldInputType::isPresentingAttachedView() const |
931 | { |
932 | return !!m_suggestionPicker; |
933 | } |
934 | |
935 | #endif |
936 | |
937 | } // namespace WebCore |
938 | |