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 "bindings/core/v8/ExceptionStatePlaceholder.h"
36 #include "core/HTMLNames.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/FrameHost.h"
45 #include "core/frame/LocalFrame.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 PassRefPtrWillBeRawPtr<DataListIndicatorElement> create(Document& document)
103 RefPtrWillBeRawPtr<DataListIndicatorElement> element = adoptRefWillBeNoop(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()
119 if (SpinButtonElement* spinButton = spinButtonElement())
120 spinButton->removeSpinButtonOwner();
124 SpinButtonElement* TextFieldInputType::spinButtonElement() const
126 return toSpinButtonElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::spinButton()));
129 bool TextFieldInputType::shouldShowFocusRingOnMouseFocus() const
134 bool TextFieldInputType::isTextField() const
139 bool TextFieldInputType::valueMissing(const String& value) const
141 return element().isRequired() && value.isEmpty();
144 bool TextFieldInputType::canSetSuggestedValue()
149 void TextFieldInputType::setValue(const String& sanitizedValue, bool valueChanged, TextFieldEventBehavior eventBehavior)
151 // Grab this input element to keep reference even if JS event handler
152 // changes input type.
153 RefPtrWillBeRawPtr<HTMLInputElement> input(element());
155 // We don't ask InputType::setValue to dispatch events because
156 // TextFieldInputType dispatches events different way from InputType.
157 InputType::setValue(sanitizedValue, valueChanged, DispatchNoEvent);
162 unsigned max = visibleValue().length();
163 if (input->focused())
164 input->setSelectionRange(max, max);
166 input->cacheSelectionInResponseToSetValue(max);
171 switch (eventBehavior) {
172 case DispatchChangeEvent:
173 // If the user is still editing this field, dispatch an input event rather than a change event.
174 // The change event will be dispatched when editing finishes.
175 if (input->focused())
176 input->dispatchFormControlInputEvent();
178 input->dispatchFormControlChangeEvent();
181 case DispatchInputAndChangeEvent: {
182 input->dispatchFormControlInputEvent();
183 input->dispatchFormControlChangeEvent();
187 case DispatchNoEvent:
191 if (!input->focused())
192 input->setTextAsOfLastFormControlChangeEvent(sanitizedValue);
195 void TextFieldInputType::handleKeydownEvent(KeyboardEvent* event)
197 if (!element().focused())
199 if (Chrome* chrome = this->chrome()) {
200 chrome->client().handleKeyboardEventOnTextField(element(), *event);
203 event->setDefaultHandled();
206 void TextFieldInputType::handleKeydownEventForSpinButton(KeyboardEvent* event)
208 if (element().isDisabledOrReadOnly())
210 const String& key = event->keyIdentifier();
213 else if (key == "Down" && !event->altKey())
214 spinButtonStepDown();
217 element().dispatchFormControlChangeEvent();
218 event->setDefaultHandled();
221 void TextFieldInputType::forwardEvent(Event* event)
223 if (SpinButtonElement* spinButton = spinButtonElement()) {
224 spinButton->forwardEvent(event);
225 if (event->defaultHandled())
229 if (element().renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(EventNames::WheelEvent) || event->type() == EventTypeNames::blur || event->type() == EventTypeNames::focus)) {
230 RenderTextControlSingleLine* renderTextControl = toRenderTextControlSingleLine(element().renderer());
231 if (event->type() == EventTypeNames::blur) {
232 if (RenderBox* innerEditorRenderer = element().innerEditorElement()->renderBox()) {
233 // FIXME: This class has no need to know about RenderLayer!
234 if (RenderLayer* innerLayer = innerEditorRenderer->layer()) {
235 if (RenderLayerScrollableArea* innerScrollableArea = innerLayer->scrollableArea()) {
236 IntSize scrollOffset(!renderTextControl->style()->isLeftToRightDirection() ? innerScrollableArea->scrollWidth().toInt() : 0, 0);
237 innerScrollableArea->scrollToOffset(scrollOffset, ScrollOffsetClamped);
242 renderTextControl->capsLockStateMayHaveChanged();
243 } else if (event->type() == EventTypeNames::focus) {
244 renderTextControl->capsLockStateMayHaveChanged();
247 element().forwardEvent(event);
251 void TextFieldInputType::handleFocusEvent(Element* oldFocusedNode, FocusType focusType)
253 InputType::handleFocusEvent(oldFocusedNode, focusType);
254 element().beginEditing();
257 void TextFieldInputType::handleBlurEvent()
259 InputType::handleBlurEvent();
260 element().endEditing();
261 if (SpinButtonElement *spinButton = spinButtonElement())
262 spinButton->releaseCapture();
265 bool TextFieldInputType::shouldSubmitImplicitly(Event* event)
267 return (event->type() == EventTypeNames::textInput && event->hasInterface(EventNames::TextEvent) && toTextEvent(event)->data() == "\n") || InputType::shouldSubmitImplicitly(event);
270 RenderObject* TextFieldInputType::createRenderer(RenderStyle*) const
272 return new RenderTextControlSingleLine(&element());
275 bool TextFieldInputType::shouldHaveSpinButton() const
277 return RenderTheme::theme().shouldHaveSpinButton(&element());
280 void TextFieldInputType::createShadowSubtree()
282 ASSERT(element().shadow());
283 ShadowRoot* shadowRoot = element().userAgentShadowRoot();
284 ASSERT(!shadowRoot->hasChildren());
286 Document& document = element().document();
287 bool shouldHaveSpinButton = this->shouldHaveSpinButton();
288 bool shouldHaveDataListIndicator = element().hasValidDataListOptions();
289 bool createsContainer = shouldHaveSpinButton || shouldHaveDataListIndicator || needsContainer();
291 RefPtrWillBeRawPtr<TextControlInnerEditorElement> innerEditor = TextControlInnerEditorElement::create(document);
292 if (!createsContainer) {
293 shadowRoot->appendChild(innerEditor.release());
297 RefPtrWillBeRawPtr<TextControlInnerContainer> container = TextControlInnerContainer::create(document);
298 container->setShadowPseudoId(AtomicString("-webkit-textfield-decoration-container", AtomicString::ConstructFromLiteral));
299 shadowRoot->appendChild(container);
301 RefPtrWillBeRawPtr<EditingViewPortElement> editingViewPort = EditingViewPortElement::create(document);
302 editingViewPort->appendChild(innerEditor.release());
303 container->appendChild(editingViewPort.release());
305 if (shouldHaveDataListIndicator)
306 container->appendChild(DataListIndicatorElement::create(document));
307 // FIXME: Because of a special handling for a spin button in
308 // RenderTextControlSingleLine, we need to put it to the last position. It's
309 // inconsistent with multiple-fields date/time types.
310 if (shouldHaveSpinButton)
311 container->appendChild(SpinButtonElement::create(document, *this));
313 // See listAttributeTargetChanged too.
316 Element* TextFieldInputType::containerElement() const
318 return element().userAgentShadowRoot()->getElementById(ShadowElementNames::textFieldContainer());
321 void TextFieldInputType::destroyShadowSubtree()
323 InputType::destroyShadowSubtree();
324 if (SpinButtonElement* spinButton = spinButtonElement())
325 spinButton->removeSpinButtonOwner();
328 void TextFieldInputType::listAttributeTargetChanged()
330 Element* picker = element().userAgentShadowRoot()->getElementById(ShadowElementNames::pickerIndicator());
331 bool didHavePickerIndicator = picker;
332 bool willHavePickerIndicator = element().hasValidDataListOptions();
333 if (didHavePickerIndicator == willHavePickerIndicator)
335 if (willHavePickerIndicator) {
336 Document& document = element().document();
337 if (Element* container = containerElement()) {
338 container->insertBefore(DataListIndicatorElement::create(document), spinButtonElement());
340 // FIXME: The following code is similar to createShadowSubtree(),
341 // but they are different. We should simplify the code by making
342 // containerElement mandatory.
343 RefPtrWillBeRawPtr<Element> rpContainer = TextControlInnerContainer::create(document);
344 rpContainer->setShadowPseudoId(AtomicString("-webkit-textfield-decoration-container", AtomicString::ConstructFromLiteral));
345 RefPtrWillBeRawPtr<Element> innerEditor = element().innerEditorElement();
346 innerEditor->parentNode()->replaceChild(rpContainer.get(), innerEditor.get());
347 RefPtrWillBeRawPtr<Element> editingViewPort = EditingViewPortElement::create(document);
348 editingViewPort->appendChild(innerEditor.release());
349 rpContainer->appendChild(editingViewPort.release());
350 rpContainer->appendChild(DataListIndicatorElement::create(document));
351 if (element().document().focusedElement() == element())
352 element().updateFocusAppearance(true /* restore selection */);
355 picker->remove(ASSERT_NO_EXCEPTION);
359 void TextFieldInputType::attributeChanged()
361 // FIXME: Updating on any attribute update should be unnecessary. We should
362 // figure out what attributes affect.
366 void TextFieldInputType::disabledAttributeChanged()
368 if (SpinButtonElement* spinButton = spinButtonElement())
369 spinButton->releaseCapture();
372 void TextFieldInputType::readonlyAttributeChanged()
374 if (SpinButtonElement* spinButton = spinButtonElement())
375 spinButton->releaseCapture();
378 bool TextFieldInputType::supportsReadOnly() const
383 static bool isASCIILineBreak(UChar c)
385 return c == '\r' || c == '\n';
388 static String limitLength(const String& string, unsigned maxLength)
390 unsigned newLength = std::min(maxLength, string.length());
391 if (newLength == string.length())
393 if (newLength > 0 && U16_IS_LEAD(string[newLength - 1]))
395 return string.left(newLength);
398 String TextFieldInputType::sanitizeValue(const String& proposedValue) const
400 return limitLength(proposedValue.removeCharacters(isASCIILineBreak), HTMLInputElement::maximumLength);
403 void TextFieldInputType::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event)
405 // Make sure that the text to be inserted will not violate the maxLength.
407 // We use HTMLInputElement::innerEditorValue() instead of
408 // HTMLInputElement::value() because they can be mismatched by
409 // sanitizeValue() in HTMLInputElement::subtreeHasChanged() in some cases.
410 unsigned oldLength = element().innerEditorValue().length();
412 // selectionLength represents the selection length of this text field to be
413 // removed by this insertion.
414 // If the text field has no focus, we don't need to take account of the
415 // selection length. The selection is the source of text drag-and-drop in
416 // that case, and nothing in the text field will be removed.
417 unsigned selectionLength = element().focused() ? plainText(element().document().frame()->selection().selection().toNormalizedRange().get()).length() : 0;
418 ASSERT(oldLength >= selectionLength);
420 // Selected characters will be removed by the next text event.
421 unsigned baseLength = oldLength - selectionLength;
422 unsigned maxLength = static_cast<unsigned>(this->maxLength()); // maxLength can never be negative.
423 unsigned appendableLength = maxLength > baseLength ? maxLength - baseLength : 0;
425 // Truncate the inserted text to avoid violating the maxLength and other constraints.
426 String eventText = event->text();
427 unsigned textLength = eventText.length();
428 while (textLength > 0 && isASCIILineBreak(eventText[textLength - 1]))
430 eventText.truncate(textLength);
431 eventText.replace("\r\n", " ");
432 eventText.replace('\r', ' ');
433 eventText.replace('\n', ' ');
435 event->setText(limitLength(eventText, appendableLength));
438 bool TextFieldInputType::shouldRespectListAttribute()
443 void TextFieldInputType::updatePlaceholderText()
445 if (!supportsPlaceholder())
447 HTMLElement* placeholder = element().placeholderElement();
448 String placeholderText = element().strippedPlaceholder();
449 if (placeholderText.isEmpty()) {
451 placeholder->remove(ASSERT_NO_EXCEPTION);
455 RefPtrWillBeRawPtr<HTMLElement> newElement = HTMLDivElement::create(element().document());
456 placeholder = newElement.get();
457 placeholder->setShadowPseudoId(AtomicString("-webkit-input-placeholder", AtomicString::ConstructFromLiteral));
458 placeholder->setAttribute(idAttr, ShadowElementNames::placeholder());
459 Element* container = containerElement();
460 Node* previous = container ? container : element().innerEditorElement();
461 previous->parentNode()->insertBefore(placeholder, previous->nextSibling());
462 ASSERT_WITH_SECURITY_IMPLICATION(placeholder->parentNode() == previous->parentNode());
464 placeholder->setTextContent(placeholderText);
467 bool TextFieldInputType::appendFormData(FormDataList& list, bool multipart) const
469 InputType::appendFormData(list, multipart);
470 const AtomicString& dirnameAttrValue = element().fastGetAttribute(dirnameAttr);
471 if (!dirnameAttrValue.isNull())
472 list.appendData(dirnameAttrValue, element().directionForFormData());
476 String TextFieldInputType::convertFromVisibleValue(const String& visibleValue) const
481 void TextFieldInputType::subtreeHasChanged()
483 ASSERT(element().renderer());
485 bool wasChanged = element().wasChangedSinceLastFormControlChangeEvent();
486 element().setChangedSinceLastFormControlChangeEvent(true);
488 // We don't need to call sanitizeUserInputValue() function here because
489 // HTMLInputElement::handleBeforeTextInsertedEvent() has already called
490 // sanitizeUserInputValue().
491 // sanitizeValue() is needed because IME input doesn't dispatch BeforeTextInsertedEvent.
492 element().setValueFromRenderer(sanitizeValue(convertFromVisibleValue(element().innerEditorValue())));
493 element().updatePlaceholderVisibility(false);
494 // Recalc for :invalid change.
495 element().setNeedsStyleRecalc(SubtreeStyleChange);
497 didSetValueByUserEdit(wasChanged ? ValueChangeStateChanged : ValueChangeStateNone);
500 void TextFieldInputType::didSetValueByUserEdit(ValueChangeState state)
502 if (!element().focused())
504 if (Chrome* chrome = this->chrome())
505 chrome->client().didChangeValueInTextField(element());
508 void TextFieldInputType::spinButtonStepDown()
510 stepUpFromRenderer(-1);
513 void TextFieldInputType::spinButtonStepUp()
515 stepUpFromRenderer(1);
518 void TextFieldInputType::updateView()
520 if (!element().suggestedValue().isNull()) {
521 element().setInnerEditorValue(element().suggestedValue());
522 element().updatePlaceholderVisibility(false);
523 } else if (element().needsToUpdateViewValue()) {
524 // Update the view only if needsToUpdateViewValue is true. It protects
525 // an unacceptable view value from being overwritten with the DOM value.
527 // e.g. <input type=number> has a view value "abc", and input.max is
528 // updated. In this case, updateView() is called but we should not
529 // update the view value.
530 element().setInnerEditorValue(visibleValue());
531 element().updatePlaceholderVisibility(false);
535 void TextFieldInputType::focusAndSelectSpinButtonOwner()
537 RefPtrWillBeRawPtr<HTMLInputElement> input(element());
542 bool TextFieldInputType::shouldSpinButtonRespondToMouseEvents()
544 return !element().isDisabledOrReadOnly();
547 bool TextFieldInputType::shouldSpinButtonRespondToWheelEvents()
549 return shouldSpinButtonRespondToMouseEvents() && element().focused();
552 void TextFieldInputType::spinButtonDidReleaseMouseCapture(SpinButtonElement::EventDispatch eventDispatch)
554 if (eventDispatch == SpinButtonElement::EventDispatchAllowed)
555 element().dispatchFormControlChangeEvent();