2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 * Copyright (C) 2011 Apple Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
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
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.
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.
33 #include "core/html/forms/TextFieldInputType.h"
35 #include "HTMLNames.h"
36 #include "bindings/v8/ExceptionStatePlaceholder.h"
37 #include "core/dom/NodeRenderStyle.h"
38 #include "core/dom/shadow/ShadowRoot.h"
39 #include "core/editing/FrameSelection.h"
40 #include "core/editing/TextIterator.h"
41 #include "core/events/BeforeTextInsertedEvent.h"
42 #include "core/events/KeyboardEvent.h"
43 #include "core/events/TextEvent.h"
44 #include "core/frame/Frame.h"
45 #include "core/frame/FrameHost.h"
46 #include "core/html/FormDataList.h"
47 #include "core/html/HTMLInputElement.h"
48 #include "core/html/shadow/ShadowElementNames.h"
49 #include "core/html/shadow/TextControlInnerElements.h"
50 #include "core/page/Chrome.h"
51 #include "core/page/ChromeClient.h"
52 #include "core/rendering/RenderDetailsMarker.h"
53 #include "core/rendering/RenderLayer.h"
54 #include "core/rendering/RenderTextControlSingleLine.h"
55 #include "core/rendering/RenderTheme.h"
56 #include "wtf/text/WTFString.h"
60 using namespace HTMLNames;
62 class DataListIndicatorElement FINAL : public HTMLDivElement {
64 inline DataListIndicatorElement(Document& document) : HTMLDivElement(document) { }
65 inline HTMLInputElement* hostInput() const { return toHTMLInputElement(shadowHost()); }
67 virtual RenderObject* createRenderer(RenderStyle*) OVERRIDE
69 return new RenderDetailsMarker(this);
72 virtual void* preDispatchEventHandler(Event* event) OVERRIDE
74 // Chromium opens autofill popup in a mousedown event listener
75 // associated to the document. We don't want to open it in this case
76 // because we opens a datalist chooser later.
77 // FIXME: We should dispatch mousedown events even in such case.
78 if (event->type() == EventTypeNames::mousedown)
79 event->stopPropagation();
83 virtual void defaultEventHandler(Event* event) OVERRIDE
85 ASSERT(document().isActive());
86 if (event->type() != EventTypeNames::click)
88 HTMLInputElement* host = hostInput();
89 if (host && !host->isDisabledOrReadOnly()) {
90 document().frameHost()->chrome().openTextDataListChooser(*host);
91 event->setDefaultHandled();
95 virtual bool willRespondToMouseClickEvents() OVERRIDE
97 return hostInput() && !hostInput()->isDisabledOrReadOnly() && document().isActive();
101 static PassRefPtr<DataListIndicatorElement> create(Document& document)
103 RefPtr<DataListIndicatorElement> element = adoptRef(new DataListIndicatorElement(document));
104 element->setShadowPseudoId(AtomicString("-webkit-calendar-picker-indicator", AtomicString::ConstructFromLiteral));
105 element->setAttribute(idAttr, ShadowElementNames::pickerIndicator());
106 return element.release();
111 TextFieldInputType::TextFieldInputType(HTMLInputElement& element)
116 TextFieldInputType::~TextFieldInputType()
118 if (SpinButtonElement* spinButton = spinButtonElement())
119 spinButton->removeSpinButtonOwner();
122 SpinButtonElement* TextFieldInputType::spinButtonElement() const
124 return toSpinButtonElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::spinButton()));
127 bool TextFieldInputType::shouldShowFocusRingOnMouseFocus() const
132 bool TextFieldInputType::isTextField() const
137 bool TextFieldInputType::valueMissing(const String& value) const
139 return element().isRequired() && value.isEmpty();
142 bool TextFieldInputType::canSetSuggestedValue()
147 void TextFieldInputType::setValue(const String& sanitizedValue, bool valueChanged, TextFieldEventBehavior eventBehavior)
149 // Grab this input element to keep reference even if JS event handler
150 // changes input type.
151 RefPtr<HTMLInputElement> input(element());
153 // We don't ask InputType::setValue to dispatch events because
154 // TextFieldInputType dispatches events different way from InputType.
155 InputType::setValue(sanitizedValue, valueChanged, DispatchNoEvent);
160 unsigned max = visibleValue().length();
161 if (input->focused())
162 input->setSelectionRange(max, max);
164 input->cacheSelectionInResponseToSetValue(max);
169 switch (eventBehavior) {
170 case DispatchChangeEvent:
171 // If the user is still editing this field, dispatch an input event rather than a change event.
172 // The change event will be dispatched when editing finishes.
173 if (input->focused())
174 input->dispatchFormControlInputEvent();
176 input->dispatchFormControlChangeEvent();
179 case DispatchInputAndChangeEvent: {
180 input->dispatchFormControlInputEvent();
181 input->dispatchFormControlChangeEvent();
185 case DispatchNoEvent:
189 if (!input->focused())
190 input->setTextAsOfLastFormControlChangeEvent(sanitizedValue);
193 void TextFieldInputType::handleKeydownEvent(KeyboardEvent* event)
195 if (!element().focused())
197 if (Chrome* chrome = this->chrome()) {
198 chrome->client().handleKeyboardEventOnTextField(element(), *event);
201 event->setDefaultHandled();
204 void TextFieldInputType::handleKeydownEventForSpinButton(KeyboardEvent* event)
206 if (element().isDisabledOrReadOnly())
208 const String& key = event->keyIdentifier();
211 else if (key == "Down")
212 spinButtonStepDown();
215 event->setDefaultHandled();
218 void TextFieldInputType::forwardEvent(Event* event)
220 if (SpinButtonElement* spinButton = spinButtonElement()) {
221 spinButton->forwardEvent(event);
222 if (event->defaultHandled())
226 if (element().renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(EventNames::WheelEvent) || event->type() == EventTypeNames::blur || event->type() == EventTypeNames::focus)) {
227 RenderTextControlSingleLine* renderTextControl = toRenderTextControlSingleLine(element().renderer());
228 if (event->type() == EventTypeNames::blur) {
229 if (RenderBox* innerTextRenderer = element().innerTextElement()->renderBox()) {
230 // FIXME: This class has no need to know about RenderLayer!
231 if (RenderLayer* innerLayer = innerTextRenderer->layer()) {
232 RenderLayerScrollableArea* innerScrollableArea = innerLayer->scrollableArea();
233 IntSize scrollOffset(!renderTextControl->style()->isLeftToRightDirection() ? innerScrollableArea->scrollWidth() : 0, 0);
234 innerScrollableArea->scrollToOffset(scrollOffset, ScrollOffsetClamped);
238 renderTextControl->capsLockStateMayHaveChanged();
239 } else if (event->type() == EventTypeNames::focus) {
240 renderTextControl->capsLockStateMayHaveChanged();
243 element().forwardEvent(event);
247 void TextFieldInputType::handleFocusEvent(Element* oldFocusedNode, FocusType focusType)
249 InputType::handleFocusEvent(oldFocusedNode, focusType);
250 element().beginEditing();
253 void TextFieldInputType::handleBlurEvent()
255 InputType::handleBlurEvent();
256 element().endEditing();
259 bool TextFieldInputType::shouldSubmitImplicitly(Event* event)
261 return (event->type() == EventTypeNames::textInput && event->hasInterface(EventNames::TextEvent) && toTextEvent(event)->data() == "\n") || InputType::shouldSubmitImplicitly(event);
264 RenderObject* TextFieldInputType::createRenderer(RenderStyle*) const
266 return new RenderTextControlSingleLine(&element());
269 bool TextFieldInputType::needsContainer() const
271 #if ENABLE(INPUT_SPEECH)
272 return element().isSpeechEnabled();
278 bool TextFieldInputType::shouldHaveSpinButton() const
280 return RenderTheme::theme().shouldHaveSpinButton(&element());
283 void TextFieldInputType::createShadowSubtree()
285 ASSERT(element().shadow());
286 ShadowRoot* shadowRoot = element().userAgentShadowRoot();
287 ASSERT(!shadowRoot->hasChildNodes());
289 Document& document = element().document();
290 bool shouldHaveSpinButton = this->shouldHaveSpinButton();
291 bool shouldHaveDataListIndicator = element().hasValidDataListOptions();
292 bool createsContainer = shouldHaveSpinButton || shouldHaveDataListIndicator || needsContainer();
294 RefPtr<TextControlInnerTextElement> innerEditor = TextControlInnerTextElement::create(document);
295 if (!createsContainer) {
296 shadowRoot->appendChild(innerEditor.release());
300 RefPtr<TextControlInnerContainer> container = TextControlInnerContainer::create(document);
301 container->setShadowPseudoId(AtomicString("-webkit-textfield-decoration-container", AtomicString::ConstructFromLiteral));
302 shadowRoot->appendChild(container);
304 RefPtr<EditingViewPortElement> editingViewPort = EditingViewPortElement::create(document);
305 editingViewPort->appendChild(innerEditor.release());
306 container->appendChild(editingViewPort.release());
308 #if ENABLE(INPUT_SPEECH)
309 if (element().isSpeechEnabled())
310 container->appendChild(InputFieldSpeechButtonElement::create(document));
313 if (shouldHaveDataListIndicator)
314 container->appendChild(DataListIndicatorElement::create(document));
315 // FIXME: Because of a special handling for a spin button in
316 // RenderTextControlSingleLine, we need to put it to the last position. It's
317 // inconsistent with multiple-fields date/time types.
318 if (shouldHaveSpinButton)
319 container->appendChild(SpinButtonElement::create(document, *this));
321 // See listAttributeTargetChanged too.
324 Element* TextFieldInputType::containerElement() const
326 return element().userAgentShadowRoot()->getElementById(ShadowElementNames::textFieldContainer());
329 void TextFieldInputType::destroyShadowSubtree()
331 InputType::destroyShadowSubtree();
332 if (SpinButtonElement* spinButton = spinButtonElement())
333 spinButton->removeSpinButtonOwner();
336 void TextFieldInputType::listAttributeTargetChanged()
338 Element* picker = element().userAgentShadowRoot()->getElementById(ShadowElementNames::pickerIndicator());
339 bool didHavePickerIndicator = picker;
340 bool willHavePickerIndicator = element().hasValidDataListOptions();
341 if (didHavePickerIndicator == willHavePickerIndicator)
343 if (willHavePickerIndicator) {
344 Document& document = element().document();
345 if (Element* container = containerElement()) {
346 container->insertBefore(DataListIndicatorElement::create(document), spinButtonElement());
348 // FIXME: The following code is similar to createShadowSubtree(),
349 // but they are different. We should simplify the code by making
350 // containerElement mandatory.
351 RefPtr<Element> rpContainer = TextControlInnerContainer::create(document);
352 rpContainer->setShadowPseudoId(AtomicString("-webkit-textfield-decoration-container", AtomicString::ConstructFromLiteral));
353 RefPtr<Element> innerEditor = element().innerTextElement();
354 innerEditor->parentNode()->replaceChild(rpContainer.get(), innerEditor.get());
355 RefPtr<Element> editingViewPort = EditingViewPortElement::create(document);
356 editingViewPort->appendChild(innerEditor.release());
357 rpContainer->appendChild(editingViewPort.release());
358 rpContainer->appendChild(DataListIndicatorElement::create(document));
361 picker->remove(ASSERT_NO_EXCEPTION);
365 void TextFieldInputType::attributeChanged()
367 // FIXME: Updating on any attribute update should be unnecessary. We should
368 // figure out what attributes affect.
372 void TextFieldInputType::disabledAttributeChanged()
374 if (SpinButtonElement* spinButton = spinButtonElement())
375 spinButton->releaseCapture();
378 void TextFieldInputType::readonlyAttributeChanged()
380 if (SpinButtonElement* spinButton = spinButtonElement())
381 spinButton->releaseCapture();
384 bool TextFieldInputType::supportsReadOnly() const
389 bool TextFieldInputType::shouldUseInputMethod() const
394 static bool isASCIILineBreak(UChar c)
396 return c == '\r' || c == '\n';
399 static String limitLength(const String& string, unsigned maxLength)
401 unsigned newLength = std::min(maxLength, string.length());
402 // FIXME: We should not truncate the string at a control character. It's not
403 // compatible with IE and Firefox.
404 for (unsigned i = 0; i < newLength; ++i) {
405 const UChar current = string[i];
406 if (current < ' ' && current != '\t') {
411 if (newLength == string.length())
413 if (newLength > 0 && U16_IS_LEAD(string[newLength - 1]))
415 return string.left(newLength);
418 String TextFieldInputType::sanitizeValue(const String& proposedValue) const
420 return limitLength(proposedValue.removeCharacters(isASCIILineBreak), HTMLInputElement::maximumLength);
423 void TextFieldInputType::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event)
425 // Make sure that the text to be inserted will not violate the maxLength.
427 // We use HTMLInputElement::innerTextValue() instead of
428 // HTMLInputElement::value() because they can be mismatched by
429 // sanitizeValue() in HTMLInputElement::subtreeHasChanged() in some cases.
430 unsigned oldLength = element().innerTextValue().length();
432 // selectionLength represents the selection length of this text field to be
433 // removed by this insertion.
434 // If the text field has no focus, we don't need to take account of the
435 // selection length. The selection is the source of text drag-and-drop in
436 // that case, and nothing in the text field will be removed.
437 unsigned selectionLength = element().focused() ? plainText(element().document().frame()->selection().selection().toNormalizedRange().get()).length() : 0;
438 ASSERT(oldLength >= selectionLength);
440 // Selected characters will be removed by the next text event.
441 unsigned baseLength = oldLength - selectionLength;
442 unsigned maxLength = static_cast<unsigned>(isTextType() ? element().maxLength() : HTMLInputElement::maximumLength); // maxLength can never be negative.
443 unsigned appendableLength = maxLength > baseLength ? maxLength - baseLength : 0;
445 // Truncate the inserted text to avoid violating the maxLength and other constraints.
446 String eventText = event->text();
447 unsigned textLength = eventText.length();
448 while (textLength > 0 && isASCIILineBreak(eventText[textLength - 1]))
450 eventText.truncate(textLength);
451 eventText.replace("\r\n", " ");
452 eventText.replace('\r', ' ');
453 eventText.replace('\n', ' ');
455 event->setText(limitLength(eventText, appendableLength));
458 bool TextFieldInputType::shouldRespectListAttribute()
460 return InputType::themeSupportsDataListUI(this);
463 void TextFieldInputType::updatePlaceholderText()
465 if (!supportsPlaceholder())
467 HTMLElement* placeholder = element().placeholderElement();
468 String placeholderText = element().strippedPlaceholder();
469 if (placeholderText.isEmpty()) {
471 placeholder->remove(ASSERT_NO_EXCEPTION);
475 RefPtr<HTMLElement> newElement = HTMLDivElement::create(element().document());
476 placeholder = newElement.get();
477 placeholder->setShadowPseudoId(AtomicString("-webkit-input-placeholder", AtomicString::ConstructFromLiteral));
478 placeholder->setAttribute(idAttr, ShadowElementNames::placeholder());
479 Element* container = containerElement();
480 Node* previous = container ? container : element().innerTextElement();
481 previous->parentNode()->insertBefore(placeholder, previous->nextSibling());
482 ASSERT_WITH_SECURITY_IMPLICATION(placeholder->parentNode() == previous->parentNode());
484 placeholder->setTextContent(placeholderText);
487 bool TextFieldInputType::appendFormData(FormDataList& list, bool multipart) const
489 InputType::appendFormData(list, multipart);
490 const AtomicString& dirnameAttrValue = element().fastGetAttribute(dirnameAttr);
491 if (!dirnameAttrValue.isNull())
492 list.appendData(dirnameAttrValue, element().directionForFormData());
496 String TextFieldInputType::convertFromVisibleValue(const String& visibleValue) const
501 void TextFieldInputType::subtreeHasChanged()
503 ASSERT(element().renderer());
505 bool wasChanged = element().wasChangedSinceLastFormControlChangeEvent();
506 element().setChangedSinceLastFormControlChangeEvent(true);
508 // We don't need to call sanitizeUserInputValue() function here because
509 // HTMLInputElement::handleBeforeTextInsertedEvent() has already called
510 // sanitizeUserInputValue().
511 // sanitizeValue() is needed because IME input doesn't dispatch BeforeTextInsertedEvent.
512 element().setValueFromRenderer(sanitizeValue(convertFromVisibleValue(element().innerTextValue())));
513 element().updatePlaceholderVisibility(false);
514 // Recalc for :invalid change.
515 element().setNeedsStyleRecalc(SubtreeStyleChange);
517 didSetValueByUserEdit(wasChanged ? ValueChangeStateChanged : ValueChangeStateNone);
520 void TextFieldInputType::didSetValueByUserEdit(ValueChangeState state)
522 if (!element().focused())
524 if (Chrome* chrome = this->chrome())
525 chrome->client().didChangeValueInTextField(element());
528 void TextFieldInputType::spinButtonStepDown()
530 stepUpFromRenderer(-1);
533 void TextFieldInputType::spinButtonStepUp()
535 stepUpFromRenderer(1);
538 void TextFieldInputType::updateView()
540 if (!element().suggestedValue().isNull()) {
541 element().setInnerTextValue(element().suggestedValue());
542 element().updatePlaceholderVisibility(false);
543 } else if (!element().formControlValueMatchesRenderer()) {
544 // Update the renderer value if the formControlValueMatchesRenderer() flag is false.
545 // It protects an unacceptable renderer value from being overwritten with the DOM value.
546 element().setInnerTextValue(visibleValue());
547 element().updatePlaceholderVisibility(false);
551 void TextFieldInputType::focusAndSelectSpinButtonOwner()
553 RefPtr<HTMLInputElement> input(element());
558 bool TextFieldInputType::shouldSpinButtonRespondToMouseEvents()
560 return !element().isDisabledOrReadOnly();
563 bool TextFieldInputType::shouldSpinButtonRespondToWheelEvents()
565 return shouldSpinButtonRespondToMouseEvents() && element().focused();
568 } // namespace WebCore