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 58 namespace blink { 59 60 using namespace HTMLNames; 61 62 class DataListIndicatorElement FINAL : public HTMLDivElement { 63 private: 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 100 public: 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 111 TextFieldInputType::TextFieldInputType(HTMLInputElement& element) 112 : InputType(element) 113 { 114 } 115 116 TextFieldInputType::~TextFieldInputType() 117 { 118 #if !ENABLE(OILPAN) 119 if (SpinButtonElement* spinButton = spinButtonElement()) 120 spinButton->removeSpinButtonOwner(); 121 #endif 122 } 123 124 SpinButtonElement* TextFieldInputType::spinButtonElement() const 125 { 126 return toSpinButtonElement(element().userAgentShadowRoot()->getElementById(ShadowElementNames::spinButton())); 127 } 128 129 bool TextFieldInputType::shouldShowFocusRingOnMouseFocus() const 130 { 131 return true; 132 } 133 134 bool TextFieldInputType::isTextField() const 135 { 136 return true; 137 } 138 139 bool TextFieldInputType::valueMissing(const String& value) const 140 { 141 return element().isRequired() && value.isEmpty(); 142 } 143 144 bool TextFieldInputType::canSetSuggestedValue() 145 { 146 return true; 147 } 148 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 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 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 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 251 void TextFieldInputType::handleFocusEvent(Element* oldFocusedNode, FocusType focusType) 252 { 253 InputType::handleFocusEvent(oldFocusedNode, focusType); 254 element().beginEditing(); 255 } 256 257 void TextFieldInputType::handleBlurEvent() 258 { 259 InputType::handleBlurEvent(); 260 element().endEditing(); 261 if (SpinButtonElement *spinButton = spinButtonElement()) 262 spinButton->releaseCapture(); 263 } 264 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 270 RenderObject* TextFieldInputType::createRenderer(RenderStyle*) const 271 { 272 return new RenderTextControlSingleLine(&element()); 273 } 274 275 bool TextFieldInputType::shouldHaveSpinButton() const 276 { 277 return RenderTheme::theme().shouldHaveSpinButton(&element()); 278 } 279 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 316 Element* TextFieldInputType::containerElement() const 317 { 318 return element().userAgentShadowRoot()->getElementById(ShadowElementNames::textFieldContainer()); 319 } 320 321 void TextFieldInputType::destroyShadowSubtree() 322 { 323 InputType::destroyShadowSubtree(); 324 if (SpinButtonElement* spinButton = spinButtonElement()) 325 spinButton->removeSpinButtonOwner(); 326 } 327 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 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 366 void TextFieldInputType::disabledAttributeChanged() 367 { 368 if (SpinButtonElement* spinButton = spinButtonElement()) 369 spinButton->releaseCapture(); 370 } 371 372 void TextFieldInputType::readonlyAttributeChanged() 373 { 374 if (SpinButtonElement* spinButton = spinButtonElement()) 375 spinButton->releaseCapture(); 376 } 377 378 bool TextFieldInputType::supportsReadOnly() const 379 { 380 return true; 381 } 382 383 static bool isASCIILineBreak(UChar c) 384 { 385 return c == '\r' || c == '\n'; 386 } 387 388 static 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 398 String TextFieldInputType::sanitizeValue(const String& proposedValue) const 399 { 400 return limitLength(proposedValue.removeCharacters(isASCIILineBreak), HTMLInputElement::maximumLength); 401 } 402 403 void 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 438 bool TextFieldInputType::shouldRespectListAttribute() 439 { 440 return true; 441 } 442 443 void 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 467 bool 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 476 String TextFieldInputType::convertFromVisibleValue(const String& visibleValue) const 477 { 478 return visibleValue; 479 } 480 481 void 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 500 void TextFieldInputType::didSetValueByUserEdit(ValueChangeState state) 501 { 502 if (!element().focused()) 503 return; 504 if (Chrome* chrome = this->chrome()) 505 chrome->client().didChangeValueInTextField(element()); 506 } 507 508 void TextFieldInputType::spinButtonStepDown() 509 { 510 stepUpFromRenderer(-1); 511 } 512 513 void TextFieldInputType::spinButtonStepUp() 514 { 515 stepUpFromRenderer(1); 516 } 517 518 void 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 535 void TextFieldInputType::focusAndSelectSpinButtonOwner() 536 { 537 RefPtrWillBeRawPtr<HTMLInputElement> input(element()); 538 input->focus(); 539 input->select(); 540 } 541 542 bool TextFieldInputType::shouldSpinButtonRespondToMouseEvents() 543 { 544 return !element().isDisabledOrReadOnly(); 545 } 546 547 bool TextFieldInputType::shouldSpinButtonRespondToWheelEvents() 548 { 549 return shouldSpinButtonRespondToMouseEvents() && element().focused(); 550 } 551 552 void TextFieldInputType::spinButtonDidReleaseMouseCapture(SpinButtonElement::EventDispatch eventDispatch) 553 { 554 if (eventDispatch == SpinButtonElement::EventDispatchAllowed) 555 element().dispatchFormControlChangeEvent(); 556 } 557 558 } // namespace blink 559