1 /*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 * Copyright (C) 2011 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 "core/html/forms/TextFieldInputType.h"
34
35 #include "bindings/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"
57
58 namespace WebCore {
59
60 using namespace HTMLNames;
61
62 class DataListIndicatorElement FINAL : public HTMLDivElement {
63 private:
DataListIndicatorElement(Document & document)64 inline DataListIndicatorElement(Document& document) : HTMLDivElement(document) { }
hostInput() const65 inline HTMLInputElement* hostInput() const { return toHTMLInputElement(shadowHost()); }
66
createRenderer(RenderStyle *)67 virtual RenderObject* createRenderer(RenderStyle*) OVERRIDE
68 {
69 return new RenderDetailsMarker(this);
70 }
71
preDispatchEventHandler(Event * event)72 virtual void* preDispatchEventHandler(Event* event) OVERRIDE
73 {
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();
80 return 0;
81 }
82
defaultEventHandler(Event * event)83 virtual void defaultEventHandler(Event* event) OVERRIDE
84 {
85 ASSERT(document().isActive());
86 if (event->type() != EventTypeNames::click)
87 return;
88 HTMLInputElement* host = hostInput();
89 if (host && !host->isDisabledOrReadOnly()) {
90 document().frameHost()->chrome().openTextDataListChooser(*host);
91 event->setDefaultHandled();
92 }
93 }
94
willRespondToMouseClickEvents()95 virtual bool willRespondToMouseClickEvents() OVERRIDE
96 {
97 return hostInput() && !hostInput()->isDisabledOrReadOnly() && document().isActive();
98 }
99
100 public:
create(Document & document)101 static PassRefPtrWillBeRawPtr<DataListIndicatorElement> create(Document& document)
102 {
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();
107 }
108
109 };
110
TextFieldInputType(HTMLInputElement & element)111 TextFieldInputType::TextFieldInputType(HTMLInputElement& element)
112 : InputType(element)
113 {
114 }
115
~TextFieldInputType()116 TextFieldInputType::~TextFieldInputType()
117 {
118 #if !ENABLE(OILPAN)
119 if (SpinButtonElement* spinButton = spinButtonElement())
120 spinButton->removeSpinButtonOwner();
121 #endif
122 }
123
spinButtonElement() const124 SpinButtonElement* TextFieldInputType::spinButtonElement() const
125 {
126 return toSpinButtonElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::spinButton()));
127 }
128
shouldShowFocusRingOnMouseFocus() const129 bool TextFieldInputType::shouldShowFocusRingOnMouseFocus() const
130 {
131 return true;
132 }
133
isTextField() const134 bool TextFieldInputType::isTextField() const
135 {
136 return true;
137 }
138
valueMissing(const String & value) const139 bool TextFieldInputType::valueMissing(const String& value) const
140 {
141 return element().isRequired() && value.isEmpty();
142 }
143
canSetSuggestedValue()144 bool TextFieldInputType::canSetSuggestedValue()
145 {
146 return true;
147 }
148
setValue(const String & sanitizedValue,bool valueChanged,TextFieldEventBehavior eventBehavior)149 void TextFieldInputType::setValue(const String& sanitizedValue, bool valueChanged, TextFieldEventBehavior eventBehavior)
150 {
151 // Grab this input element to keep reference even if JS event handler
152 // changes input type.
153 RefPtrWillBeRawPtr<HTMLInputElement> input(element());
154
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);
158
159 if (valueChanged)
160 input->updateView();
161
162 unsigned max = visibleValue().length();
163 if (input->focused())
164 input->setSelectionRange(max, max);
165 else
166 input->cacheSelectionInResponseToSetValue(max);
167
168 if (!valueChanged)
169 return;
170
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();
177 else
178 input->dispatchFormControlChangeEvent();
179 break;
180
181 case DispatchInputAndChangeEvent: {
182 input->dispatchFormControlInputEvent();
183 input->dispatchFormControlChangeEvent();
184 break;
185 }
186
187 case DispatchNoEvent:
188 break;
189 }
190
191 if (!input->focused())
192 input->setTextAsOfLastFormControlChangeEvent(sanitizedValue);
193 }
194
handleKeydownEvent(KeyboardEvent * event)195 void TextFieldInputType::handleKeydownEvent(KeyboardEvent* event)
196 {
197 if (!element().focused())
198 return;
199 if (Chrome* chrome = this->chrome()) {
200 chrome->client().handleKeyboardEventOnTextField(element(), *event);
201 return;
202 }
203 event->setDefaultHandled();
204 }
205
handleKeydownEventForSpinButton(KeyboardEvent * event)206 void TextFieldInputType::handleKeydownEventForSpinButton(KeyboardEvent* event)
207 {
208 if (element().isDisabledOrReadOnly())
209 return;
210 const String& key = event->keyIdentifier();
211 if (key == "Up")
212 spinButtonStepUp();
213 else if (key == "Down" && !event->altKey())
214 spinButtonStepDown();
215 else
216 return;
217 element().dispatchFormControlChangeEvent();
218 event->setDefaultHandled();
219 }
220
forwardEvent(Event * event)221 void TextFieldInputType::forwardEvent(Event* event)
222 {
223 if (SpinButtonElement* spinButton = spinButtonElement()) {
224 spinButton->forwardEvent(event);
225 if (event->defaultHandled())
226 return;
227 }
228
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);
238 }
239 }
240 }
241
242 renderTextControl->capsLockStateMayHaveChanged();
243 } else if (event->type() == EventTypeNames::focus) {
244 renderTextControl->capsLockStateMayHaveChanged();
245 }
246
247 element().forwardEvent(event);
248 }
249 }
250
handleFocusEvent(Element * oldFocusedNode,FocusType focusType)251 void TextFieldInputType::handleFocusEvent(Element* oldFocusedNode, FocusType focusType)
252 {
253 InputType::handleFocusEvent(oldFocusedNode, focusType);
254 element().beginEditing();
255 }
256
handleBlurEvent()257 void TextFieldInputType::handleBlurEvent()
258 {
259 InputType::handleBlurEvent();
260 element().endEditing();
261 if (SpinButtonElement *spinButton = spinButtonElement())
262 spinButton->releaseCapture();
263 }
264
shouldSubmitImplicitly(Event * event)265 bool TextFieldInputType::shouldSubmitImplicitly(Event* event)
266 {
267 return (event->type() == EventTypeNames::textInput && event->hasInterface(EventNames::TextEvent) && toTextEvent(event)->data() == "\n") || InputType::shouldSubmitImplicitly(event);
268 }
269
createRenderer(RenderStyle *) const270 RenderObject* TextFieldInputType::createRenderer(RenderStyle*) const
271 {
272 return new RenderTextControlSingleLine(&element());
273 }
274
shouldHaveSpinButton() const275 bool TextFieldInputType::shouldHaveSpinButton() const
276 {
277 return RenderTheme::theme().shouldHaveSpinButton(&element());
278 }
279
createShadowSubtree()280 void TextFieldInputType::createShadowSubtree()
281 {
282 ASSERT(element().shadow());
283 ShadowRoot* shadowRoot = element().userAgentShadowRoot();
284 ASSERT(!shadowRoot->hasChildren());
285
286 Document& document = element().document();
287 bool shouldHaveSpinButton = this->shouldHaveSpinButton();
288 bool shouldHaveDataListIndicator = element().hasValidDataListOptions();
289 bool createsContainer = shouldHaveSpinButton || shouldHaveDataListIndicator || needsContainer();
290
291 RefPtrWillBeRawPtr<TextControlInnerEditorElement> innerEditor = TextControlInnerEditorElement::create(document);
292 if (!createsContainer) {
293 shadowRoot->appendChild(innerEditor.release());
294 return;
295 }
296
297 RefPtrWillBeRawPtr<TextControlInnerContainer> container = TextControlInnerContainer::create(document);
298 container->setShadowPseudoId(AtomicString("-webkit-textfield-decoration-container", AtomicString::ConstructFromLiteral));
299 shadowRoot->appendChild(container);
300
301 RefPtrWillBeRawPtr<EditingViewPortElement> editingViewPort = EditingViewPortElement::create(document);
302 editingViewPort->appendChild(innerEditor.release());
303 container->appendChild(editingViewPort.release());
304
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));
312
313 // See listAttributeTargetChanged too.
314 }
315
containerElement() const316 Element* TextFieldInputType::containerElement() const
317 {
318 return element().userAgentShadowRoot()->getElementById(ShadowElementNames::textFieldContainer());
319 }
320
destroyShadowSubtree()321 void TextFieldInputType::destroyShadowSubtree()
322 {
323 InputType::destroyShadowSubtree();
324 if (SpinButtonElement* spinButton = spinButtonElement())
325 spinButton->removeSpinButtonOwner();
326 }
327
listAttributeTargetChanged()328 void TextFieldInputType::listAttributeTargetChanged()
329 {
330 Element* picker = element().userAgentShadowRoot()->getElementById(ShadowElementNames::pickerIndicator());
331 bool didHavePickerIndicator = picker;
332 bool willHavePickerIndicator = element().hasValidDataListOptions();
333 if (didHavePickerIndicator == willHavePickerIndicator)
334 return;
335 if (willHavePickerIndicator) {
336 Document& document = element().document();
337 if (Element* container = containerElement()) {
338 container->insertBefore(DataListIndicatorElement::create(document), spinButtonElement());
339 } else {
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 */);
353 }
354 } else {
355 picker->remove(ASSERT_NO_EXCEPTION);
356 }
357 }
358
attributeChanged()359 void TextFieldInputType::attributeChanged()
360 {
361 // FIXME: Updating on any attribute update should be unnecessary. We should
362 // figure out what attributes affect.
363 updateView();
364 }
365
disabledAttributeChanged()366 void TextFieldInputType::disabledAttributeChanged()
367 {
368 if (SpinButtonElement* spinButton = spinButtonElement())
369 spinButton->releaseCapture();
370 }
371
readonlyAttributeChanged()372 void TextFieldInputType::readonlyAttributeChanged()
373 {
374 if (SpinButtonElement* spinButton = spinButtonElement())
375 spinButton->releaseCapture();
376 }
377
supportsReadOnly() const378 bool TextFieldInputType::supportsReadOnly() const
379 {
380 return true;
381 }
382
shouldUseInputMethod() const383 bool TextFieldInputType::shouldUseInputMethod() const
384 {
385 return true;
386 }
387
isASCIILineBreak(UChar c)388 static bool isASCIILineBreak(UChar c)
389 {
390 return c == '\r' || c == '\n';
391 }
392
limitLength(const String & string,unsigned maxLength)393 static String limitLength(const String& string, unsigned maxLength)
394 {
395 unsigned newLength = std::min(maxLength, string.length());
396 if (newLength == string.length())
397 return string;
398 if (newLength > 0 && U16_IS_LEAD(string[newLength - 1]))
399 --newLength;
400 return string.left(newLength);
401 }
402
sanitizeValue(const String & proposedValue) const403 String TextFieldInputType::sanitizeValue(const String& proposedValue) const
404 {
405 return limitLength(proposedValue.removeCharacters(isASCIILineBreak), HTMLInputElement::maximumLength);
406 }
407
handleBeforeTextInsertedEvent(BeforeTextInsertedEvent * event)408 void TextFieldInputType::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event)
409 {
410 // Make sure that the text to be inserted will not violate the maxLength.
411
412 // We use HTMLInputElement::innerEditorValue() instead of
413 // HTMLInputElement::value() because they can be mismatched by
414 // sanitizeValue() in HTMLInputElement::subtreeHasChanged() in some cases.
415 unsigned oldLength = element().innerEditorValue().length();
416
417 // selectionLength represents the selection length of this text field to be
418 // removed by this insertion.
419 // If the text field has no focus, we don't need to take account of the
420 // selection length. The selection is the source of text drag-and-drop in
421 // that case, and nothing in the text field will be removed.
422 unsigned selectionLength = element().focused() ? plainText(element().document().frame()->selection().selection().toNormalizedRange().get()).length() : 0;
423 ASSERT(oldLength >= selectionLength);
424
425 // Selected characters will be removed by the next text event.
426 unsigned baseLength = oldLength - selectionLength;
427 unsigned maxLength = static_cast<unsigned>(isTextType() ? element().maxLength() : HTMLInputElement::maximumLength); // maxLength can never be negative.
428 unsigned appendableLength = maxLength > baseLength ? maxLength - baseLength : 0;
429
430 // Truncate the inserted text to avoid violating the maxLength and other constraints.
431 String eventText = event->text();
432 unsigned textLength = eventText.length();
433 while (textLength > 0 && isASCIILineBreak(eventText[textLength - 1]))
434 textLength--;
435 eventText.truncate(textLength);
436 eventText.replace("\r\n", " ");
437 eventText.replace('\r', ' ');
438 eventText.replace('\n', ' ');
439
440 event->setText(limitLength(eventText, appendableLength));
441 }
442
shouldRespectListAttribute()443 bool TextFieldInputType::shouldRespectListAttribute()
444 {
445 return true;
446 }
447
updatePlaceholderText()448 void TextFieldInputType::updatePlaceholderText()
449 {
450 if (!supportsPlaceholder())
451 return;
452 HTMLElement* placeholder = element().placeholderElement();
453 String placeholderText = element().strippedPlaceholder();
454 if (placeholderText.isEmpty()) {
455 if (placeholder)
456 placeholder->remove(ASSERT_NO_EXCEPTION);
457 return;
458 }
459 if (!placeholder) {
460 RefPtrWillBeRawPtr<HTMLElement> newElement = HTMLDivElement::create(element().document());
461 placeholder = newElement.get();
462 placeholder->setShadowPseudoId(AtomicString("-webkit-input-placeholder", AtomicString::ConstructFromLiteral));
463 placeholder->setAttribute(idAttr, ShadowElementNames::placeholder());
464 Element* container = containerElement();
465 Node* previous = container ? container : element().innerEditorElement();
466 previous->parentNode()->insertBefore(placeholder, previous->nextSibling());
467 ASSERT_WITH_SECURITY_IMPLICATION(placeholder->parentNode() == previous->parentNode());
468 }
469 placeholder->setTextContent(placeholderText);
470 }
471
appendFormData(FormDataList & list,bool multipart) const472 bool TextFieldInputType::appendFormData(FormDataList& list, bool multipart) const
473 {
474 InputType::appendFormData(list, multipart);
475 const AtomicString& dirnameAttrValue = element().fastGetAttribute(dirnameAttr);
476 if (!dirnameAttrValue.isNull())
477 list.appendData(dirnameAttrValue, element().directionForFormData());
478 return true;
479 }
480
convertFromVisibleValue(const String & visibleValue) const481 String TextFieldInputType::convertFromVisibleValue(const String& visibleValue) const
482 {
483 return visibleValue;
484 }
485
subtreeHasChanged()486 void TextFieldInputType::subtreeHasChanged()
487 {
488 ASSERT(element().renderer());
489
490 bool wasChanged = element().wasChangedSinceLastFormControlChangeEvent();
491 element().setChangedSinceLastFormControlChangeEvent(true);
492
493 // We don't need to call sanitizeUserInputValue() function here because
494 // HTMLInputElement::handleBeforeTextInsertedEvent() has already called
495 // sanitizeUserInputValue().
496 // sanitizeValue() is needed because IME input doesn't dispatch BeforeTextInsertedEvent.
497 element().setValueFromRenderer(sanitizeValue(convertFromVisibleValue(element().innerEditorValue())));
498 element().updatePlaceholderVisibility(false);
499 // Recalc for :invalid change.
500 element().setNeedsStyleRecalc(SubtreeStyleChange);
501
502 didSetValueByUserEdit(wasChanged ? ValueChangeStateChanged : ValueChangeStateNone);
503 }
504
didSetValueByUserEdit(ValueChangeState state)505 void TextFieldInputType::didSetValueByUserEdit(ValueChangeState state)
506 {
507 if (!element().focused())
508 return;
509 if (Chrome* chrome = this->chrome())
510 chrome->client().didChangeValueInTextField(element());
511 }
512
spinButtonStepDown()513 void TextFieldInputType::spinButtonStepDown()
514 {
515 stepUpFromRenderer(-1);
516 }
517
spinButtonStepUp()518 void TextFieldInputType::spinButtonStepUp()
519 {
520 stepUpFromRenderer(1);
521 }
522
updateView()523 void TextFieldInputType::updateView()
524 {
525 if (!element().suggestedValue().isNull()) {
526 element().setInnerEditorValue(element().suggestedValue());
527 element().updatePlaceholderVisibility(false);
528 } else if (element().needsToUpdateViewValue()) {
529 // Update the view only if needsToUpdateViewValue is true. It protects
530 // an unacceptable view value from being overwritten with the DOM value.
531 //
532 // e.g. <input type=number> has a view value "abc", and input.max is
533 // updated. In this case, updateView() is called but we should not
534 // update the view value.
535 element().setInnerEditorValue(visibleValue());
536 element().updatePlaceholderVisibility(false);
537 }
538 }
539
focusAndSelectSpinButtonOwner()540 void TextFieldInputType::focusAndSelectSpinButtonOwner()
541 {
542 RefPtrWillBeRawPtr<HTMLInputElement> input(element());
543 input->focus();
544 input->select();
545 }
546
shouldSpinButtonRespondToMouseEvents()547 bool TextFieldInputType::shouldSpinButtonRespondToMouseEvents()
548 {
549 return !element().isDisabledOrReadOnly();
550 }
551
shouldSpinButtonRespondToWheelEvents()552 bool TextFieldInputType::shouldSpinButtonRespondToWheelEvents()
553 {
554 return shouldSpinButtonRespondToMouseEvents() && element().focused();
555 }
556
spinButtonDidReleaseMouseCapture(SpinButtonElement::EventDispatch eventDispatch)557 void TextFieldInputType::spinButtonDidReleaseMouseCapture(SpinButtonElement::EventDispatch eventDispatch)
558 {
559 if (eventDispatch == SpinButtonElement::EventDispatchAllowed)
560 element().dispatchFormControlChangeEvent();
561 }
562
563 } // namespace WebCore
564