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/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" 57 58namespace blink { 59 60using namespace HTMLNames; 61 62class DataListIndicatorElement FINAL : public HTMLDivElement { 63private: 64 inline DataListIndicatorElement(Document& document) : HTMLDivElement(document) { } 65 inline HTMLInputElement* hostInput() const { return toHTMLInputElement(shadowHost()); } 66 67 virtual RenderObject* createRenderer(RenderStyle*) OVERRIDE 68 { 69 return new RenderDetailsMarker(this); 70 } 71 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 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 95 virtual bool willRespondToMouseClickEvents() OVERRIDE 96 { 97 return hostInput() && !hostInput()->isDisabledOrReadOnly() && document().isActive(); 98 } 99 100public: 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 111TextFieldInputType::TextFieldInputType(HTMLInputElement& element) 112 : InputType(element) 113{ 114} 115 116TextFieldInputType::~TextFieldInputType() 117{ 118#if !ENABLE(OILPAN) 119 if (SpinButtonElement* spinButton = spinButtonElement()) 120 spinButton->removeSpinButtonOwner(); 121#endif 122} 123 124SpinButtonElement* TextFieldInputType::spinButtonElement() const 125{ 126 return toSpinButtonElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::spinButton())); 127} 128 129bool TextFieldInputType::shouldShowFocusRingOnMouseFocus() const 130{ 131 return true; 132} 133 134bool TextFieldInputType::isTextField() const 135{ 136 return true; 137} 138 139bool TextFieldInputType::valueMissing(const String& value) const 140{ 141 return element().isRequired() && value.isEmpty(); 142} 143 144bool TextFieldInputType::canSetSuggestedValue() 145{ 146 return true; 147} 148 149void 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 195void 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 206void 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 221void 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 251void TextFieldInputType::handleFocusEvent(Element* oldFocusedNode, FocusType focusType) 252{ 253 InputType::handleFocusEvent(oldFocusedNode, focusType); 254 element().beginEditing(); 255} 256 257void TextFieldInputType::handleBlurEvent() 258{ 259 InputType::handleBlurEvent(); 260 element().endEditing(); 261 if (SpinButtonElement *spinButton = spinButtonElement()) 262 spinButton->releaseCapture(); 263} 264 265bool TextFieldInputType::shouldSubmitImplicitly(Event* event) 266{ 267 return (event->type() == EventTypeNames::textInput && event->hasInterface(EventNames::TextEvent) && toTextEvent(event)->data() == "\n") || InputType::shouldSubmitImplicitly(event); 268} 269 270RenderObject* TextFieldInputType::createRenderer(RenderStyle*) const 271{ 272 return new RenderTextControlSingleLine(&element()); 273} 274 275bool TextFieldInputType::shouldHaveSpinButton() const 276{ 277 return RenderTheme::theme().shouldHaveSpinButton(&element()); 278} 279 280void 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 316Element* TextFieldInputType::containerElement() const 317{ 318 return element().userAgentShadowRoot()->getElementById(ShadowElementNames::textFieldContainer()); 319} 320 321void TextFieldInputType::destroyShadowSubtree() 322{ 323 InputType::destroyShadowSubtree(); 324 if (SpinButtonElement* spinButton = spinButtonElement()) 325 spinButton->removeSpinButtonOwner(); 326} 327 328void 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 359void 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 366void TextFieldInputType::disabledAttributeChanged() 367{ 368 if (SpinButtonElement* spinButton = spinButtonElement()) 369 spinButton->releaseCapture(); 370} 371 372void TextFieldInputType::readonlyAttributeChanged() 373{ 374 if (SpinButtonElement* spinButton = spinButtonElement()) 375 spinButton->releaseCapture(); 376} 377 378bool TextFieldInputType::supportsReadOnly() const 379{ 380 return true; 381} 382 383static bool isASCIILineBreak(UChar c) 384{ 385 return c == '\r' || c == '\n'; 386} 387 388static String limitLength(const String& string, unsigned maxLength) 389{ 390 unsigned newLength = std::min(maxLength, string.length()); 391 if (newLength == string.length()) 392 return string; 393 if (newLength > 0 && U16_IS_LEAD(string[newLength - 1])) 394 --newLength; 395 return string.left(newLength); 396} 397 398String TextFieldInputType::sanitizeValue(const String& proposedValue) const 399{ 400 return limitLength(proposedValue.removeCharacters(isASCIILineBreak), HTMLInputElement::maximumLength); 401} 402 403void TextFieldInputType::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event) 404{ 405 // Make sure that the text to be inserted will not violate the maxLength. 406 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(); 411 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); 419 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; 424 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])) 429 textLength--; 430 eventText.truncate(textLength); 431 eventText.replace("\r\n", " "); 432 eventText.replace('\r', ' '); 433 eventText.replace('\n', ' '); 434 435 event->setText(limitLength(eventText, appendableLength)); 436} 437 438bool TextFieldInputType::shouldRespectListAttribute() 439{ 440 return true; 441} 442 443void TextFieldInputType::updatePlaceholderText() 444{ 445 if (!supportsPlaceholder()) 446 return; 447 HTMLElement* placeholder = element().placeholderElement(); 448 String placeholderText = element().strippedPlaceholder(); 449 if (placeholderText.isEmpty()) { 450 if (placeholder) 451 placeholder->remove(ASSERT_NO_EXCEPTION); 452 return; 453 } 454 if (!placeholder) { 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()); 463 } 464 placeholder->setTextContent(placeholderText); 465} 466 467bool TextFieldInputType::appendFormData(FormDataList& list, bool multipart) const 468{ 469 InputType::appendFormData(list, multipart); 470 const AtomicString& dirnameAttrValue = element().fastGetAttribute(dirnameAttr); 471 if (!dirnameAttrValue.isNull()) 472 list.appendData(dirnameAttrValue, element().directionForFormData()); 473 return true; 474} 475 476String TextFieldInputType::convertFromVisibleValue(const String& visibleValue) const 477{ 478 return visibleValue; 479} 480 481void TextFieldInputType::subtreeHasChanged() 482{ 483 ASSERT(element().renderer()); 484 485 bool wasChanged = element().wasChangedSinceLastFormControlChangeEvent(); 486 element().setChangedSinceLastFormControlChangeEvent(true); 487 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); 496 497 didSetValueByUserEdit(wasChanged ? ValueChangeStateChanged : ValueChangeStateNone); 498} 499 500void TextFieldInputType::didSetValueByUserEdit(ValueChangeState state) 501{ 502 if (!element().focused()) 503 return; 504 if (Chrome* chrome = this->chrome()) 505 chrome->client().didChangeValueInTextField(element()); 506} 507 508void TextFieldInputType::spinButtonStepDown() 509{ 510 stepUpFromRenderer(-1); 511} 512 513void TextFieldInputType::spinButtonStepUp() 514{ 515 stepUpFromRenderer(1); 516} 517 518void TextFieldInputType::updateView() 519{ 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. 526 // 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); 532 } 533} 534 535void TextFieldInputType::focusAndSelectSpinButtonOwner() 536{ 537 RefPtrWillBeRawPtr<HTMLInputElement> input(element()); 538 input->focus(); 539 input->select(); 540} 541 542bool TextFieldInputType::shouldSpinButtonRespondToMouseEvents() 543{ 544 return !element().isDisabledOrReadOnly(); 545} 546 547bool TextFieldInputType::shouldSpinButtonRespondToWheelEvents() 548{ 549 return shouldSpinButtonRespondToMouseEvents() && element().focused(); 550} 551 552void TextFieldInputType::spinButtonDidReleaseMouseCapture(SpinButtonElement::EventDispatch eventDispatch) 553{ 554 if (eventDispatch == SpinButtonElement::EventDispatchAllowed) 555 element().dispatchFormControlChangeEvent(); 556} 557 558} // namespace blink 559