blob: 6bdab976046589f6a0e62eb9fe5a5ac1c3dfe4c4 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
* Copyright (C) 2011 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/html/forms/TextFieldInputType.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/HTMLNames.h"
#include "core/dom/ShadowRoot.h"
#include "core/editing/FrameSelection.h"
#include "core/events/BeforeTextInsertedEvent.h"
#include "core/events/KeyboardEvent.h"
#include "core/events/TextEvent.h"
#include "core/frame/LocalFrame.h"
#include "core/html/FormData.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/forms/TextControlInnerElements.h"
#include "core/html/shadow/ShadowElementNames.h"
#include "core/layout/LayoutDetailsMarker.h"
#include "core/layout/LayoutTextControlSingleLine.h"
#include "core/layout/LayoutTheme.h"
#include "core/page/ChromeClient.h"
#include "core/page/Page.h"
#include "core/paint/PaintLayer.h"
#include "platform/EventDispatchForbiddenScope.h"
#include "platform/wtf/text/WTFString.h"
namespace blink {
using namespace HTMLNames;
class DataListIndicatorElement final : public HTMLDivElement {
private:
inline DataListIndicatorElement(Document& document)
: HTMLDivElement(document) {}
inline HTMLInputElement* HostInput() const {
return ToHTMLInputElement(OwnerShadowHost());
}
LayoutObject* CreateLayoutObject(const ComputedStyle&) override {
return new LayoutDetailsMarker(this);
}
EventDispatchHandlingState* PreDispatchEventHandler(Event* event) override {
// Chromium opens autofill popup in a mousedown event listener
// associated to the document. We don't want to open it in this case
// because we opens a datalist chooser later.
// FIXME: We should dispatch mousedown events even in such case.
if (event->type() == EventTypeNames::mousedown)
event->stopPropagation();
return nullptr;
}
void DefaultEventHandler(Event* event) override {
DCHECK(GetDocument().IsActive());
if (event->type() != EventTypeNames::click)
return;
HTMLInputElement* host = HostInput();
if (host && !host->IsDisabledOrReadOnly()) {
GetDocument().GetPage()->GetChromeClient().OpenTextDataListChooser(*host);
event->SetDefaultHandled();
}
}
bool WillRespondToMouseClickEvents() override {
return HostInput() && !HostInput()->IsDisabledOrReadOnly() &&
GetDocument().IsActive();
}
public:
static DataListIndicatorElement* Create(Document& document) {
DataListIndicatorElement* element = new DataListIndicatorElement(document);
element->SetShadowPseudoId(
AtomicString("-webkit-calendar-picker-indicator"));
element->setAttribute(idAttr, ShadowElementNames::PickerIndicator());
return element;
}
};
TextFieldInputType::TextFieldInputType(HTMLInputElement& element)
: InputType(element), InputTypeView(element) {}
TextFieldInputType::~TextFieldInputType() {}
DEFINE_TRACE(TextFieldInputType) {
InputTypeView::Trace(visitor);
InputType::Trace(visitor);
}
InputTypeView* TextFieldInputType::CreateView() {
return this;
}
InputType::ValueMode TextFieldInputType::GetValueMode() const {
return ValueMode::kValue;
}
SpinButtonElement* TextFieldInputType::GetSpinButtonElement() const {
return ToSpinButtonElementOrDie(
GetElement().UserAgentShadowRoot()->getElementById(
ShadowElementNames::SpinButton()));
}
bool TextFieldInputType::ShouldShowFocusRingOnMouseFocus() const {
return true;
}
bool TextFieldInputType::IsTextField() const {
return true;
}
bool TextFieldInputType::ValueMissing(const String& value) const {
return GetElement().IsRequired() && value.IsEmpty();
}
bool TextFieldInputType::CanSetSuggestedValue() {
return true;
}
void TextFieldInputType::SetValue(const String& sanitized_value,
bool value_changed,
TextFieldEventBehavior event_behavior,
TextControlSetValueSelection selection) {
// We don't use InputType::setValue. TextFieldInputType dispatches events
// different way from InputType::setValue.
if (event_behavior == kDispatchNoEvent)
GetElement().SetNonAttributeValue(sanitized_value);
else
GetElement().SetNonAttributeValueByUserEdit(sanitized_value);
// The following early-return can't be moved to the beginning of this
// function. We need to update non-attribute value even if the value is not
// changed. For example, <input type=number> has a badInput string, that is
// to say, IDL value=="", and new value is "", which should clear the badInput
// string and update validiity.
if (!value_changed)
return;
GetElement().UpdateView();
if (selection == TextControlSetValueSelection::kSetSelectionToEnd) {
unsigned max = VisibleValue().length();
GetElement().SetSelectionRange(max, max);
}
switch (event_behavior) {
case kDispatchChangeEvent:
// If the user is still editing this field, dispatch an input event rather
// than a change event. The change event will be dispatched when editing
// finishes.
if (GetElement().IsFocused())
GetElement().DispatchInputEvent();
else
GetElement().DispatchFormControlChangeEvent();
break;
case kDispatchInputAndChangeEvent: {
GetElement().DispatchInputEvent();
GetElement().DispatchFormControlChangeEvent();
break;
}
case kDispatchNoEvent:
break;
}
}
void TextFieldInputType::HandleKeydownEvent(KeyboardEvent* event) {
if (!GetElement().IsFocused())
return;
if (ChromeClient* chrome_client = this->GetChromeClient()) {
chrome_client->HandleKeyboardEventOnTextField(GetElement(), *event);
return;
}
event->SetDefaultHandled();
}
void TextFieldInputType::HandleKeydownEventForSpinButton(KeyboardEvent* event) {
if (GetElement().IsDisabledOrReadOnly())
return;
const String& key = event->key();
if (key == "ArrowUp")
SpinButtonStepUp();
else if (key == "ArrowDown" && !event->altKey())
SpinButtonStepDown();
else
return;
GetElement().DispatchFormControlChangeEvent();
event->SetDefaultHandled();
}
void TextFieldInputType::ForwardEvent(Event* event) {
if (SpinButtonElement* spin_button = GetSpinButtonElement()) {
spin_button->ForwardEvent(event);
if (event->DefaultHandled())
return;
}
if (GetElement().GetLayoutObject() &&
(event->IsMouseEvent() || event->IsDragEvent() ||
event->HasInterface(EventNames::WheelEvent) ||
event->type() == EventTypeNames::blur ||
event->type() == EventTypeNames::focus)) {
LayoutTextControlSingleLine* layout_text_control =
ToLayoutTextControlSingleLine(GetElement().GetLayoutObject());
if (event->type() == EventTypeNames::blur) {
if (LayoutBox* inner_editor_layout_object =
GetElement().InnerEditorElement()->GetLayoutBox()) {
// FIXME: This class has no need to know about PaintLayer!
if (PaintLayer* inner_layer = inner_editor_layout_object->Layer()) {
if (PaintLayerScrollableArea* inner_scrollable_area =
inner_layer->GetScrollableArea()) {
inner_scrollable_area->SetScrollOffset(ScrollOffset(0, 0),
kProgrammaticScroll);
}
}
}
layout_text_control->CapsLockStateMayHaveChanged();
} else if (event->type() == EventTypeNames::focus) {
layout_text_control->CapsLockStateMayHaveChanged();
}
GetElement().ForwardEvent(event);
}
}
void TextFieldInputType::HandleBlurEvent() {
InputTypeView::HandleBlurEvent();
GetElement().EndEditing();
if (SpinButtonElement* spin_button = GetSpinButtonElement())
spin_button->ReleaseCapture();
}
bool TextFieldInputType::ShouldSubmitImplicitly(Event* event) {
return (event->type() == EventTypeNames::textInput &&
event->HasInterface(EventNames::TextEvent) &&
ToTextEvent(event)->data() == "\n") ||
InputTypeView::ShouldSubmitImplicitly(event);
}
LayoutObject* TextFieldInputType::CreateLayoutObject(
const ComputedStyle&) const {
return new LayoutTextControlSingleLine(&GetElement());
}
bool TextFieldInputType::ShouldHaveSpinButton() const {
return LayoutTheme::GetTheme().ShouldHaveSpinButton(&GetElement());
}
void TextFieldInputType::CreateShadowSubtree() {
DCHECK(GetElement().Shadow());
ShadowRoot* shadow_root = GetElement().UserAgentShadowRoot();
DCHECK(!shadow_root->HasChildren());
Document& document = GetElement().GetDocument();
bool should_have_spin_button = this->ShouldHaveSpinButton();
bool should_have_data_list_indicator = GetElement().HasValidDataListOptions();
bool creates_container = should_have_spin_button ||
should_have_data_list_indicator || NeedsContainer();
TextControlInnerEditorElement* inner_editor =
TextControlInnerEditorElement::Create(document);
if (!creates_container) {
shadow_root->AppendChild(inner_editor);
return;
}
TextControlInnerContainer* container =
TextControlInnerContainer::Create(document);
container->SetShadowPseudoId(
AtomicString("-webkit-textfield-decoration-container"));
shadow_root->AppendChild(container);
EditingViewPortElement* editing_view_port =
EditingViewPortElement::Create(document);
editing_view_port->AppendChild(inner_editor);
container->AppendChild(editing_view_port);
if (should_have_data_list_indicator)
container->AppendChild(DataListIndicatorElement::Create(document));
// FIXME: Because of a special handling for a spin button in
// LayoutTextControlSingleLine, we need to put it to the last position. It's
// inconsistent with multiple-fields date/time types.
if (should_have_spin_button)
container->AppendChild(SpinButtonElement::Create(document, *this));
// See listAttributeTargetChanged too.
}
Element* TextFieldInputType::ContainerElement() const {
return GetElement().UserAgentShadowRoot()->getElementById(
ShadowElementNames::TextFieldContainer());
}
void TextFieldInputType::DestroyShadowSubtree() {
InputTypeView::DestroyShadowSubtree();
if (SpinButtonElement* spin_button = GetSpinButtonElement())
spin_button->RemoveSpinButtonOwner();
}
void TextFieldInputType::ListAttributeTargetChanged() {
if (ChromeClient* chrome_client = this->GetChromeClient())
chrome_client->TextFieldDataListChanged(GetElement());
Element* picker = GetElement().UserAgentShadowRoot()->getElementById(
ShadowElementNames::PickerIndicator());
bool did_have_picker_indicator = picker;
bool will_have_picker_indicator = GetElement().HasValidDataListOptions();
if (did_have_picker_indicator == will_have_picker_indicator)
return;
EventDispatchForbiddenScope::AllowUserAgentEvents allow_events;
if (will_have_picker_indicator) {
Document& document = GetElement().GetDocument();
if (Element* container = ContainerElement()) {
container->InsertBefore(DataListIndicatorElement::Create(document),
GetSpinButtonElement());
} else {
// FIXME: The following code is similar to createShadowSubtree(),
// but they are different. We should simplify the code by making
// containerElement mandatory.
Element* rp_container = TextControlInnerContainer::Create(document);
rp_container->SetShadowPseudoId(
AtomicString("-webkit-textfield-decoration-container"));
Element* inner_editor = GetElement().InnerEditorElement();
inner_editor->parentNode()->ReplaceChild(rp_container, inner_editor);
Element* editing_view_port = EditingViewPortElement::Create(document);
editing_view_port->AppendChild(inner_editor);
rp_container->AppendChild(editing_view_port);
rp_container->AppendChild(DataListIndicatorElement::Create(document));
if (GetElement().GetDocument().FocusedElement() == GetElement())
GetElement().UpdateFocusAppearance(SelectionBehaviorOnFocus::kRestore);
}
} else {
picker->remove(ASSERT_NO_EXCEPTION);
}
}
void TextFieldInputType::AttributeChanged() {
// FIXME: Updating on any attribute update should be unnecessary. We should
// figure out what attributes affect.
UpdateView();
}
void TextFieldInputType::DisabledAttributeChanged() {
if (SpinButtonElement* spin_button = GetSpinButtonElement())
spin_button->ReleaseCapture();
}
void TextFieldInputType::ReadonlyAttributeChanged() {
if (SpinButtonElement* spin_button = GetSpinButtonElement())
spin_button->ReleaseCapture();
}
bool TextFieldInputType::SupportsReadOnly() const {
return true;
}
static bool IsASCIILineBreak(UChar c) {
return c == '\r' || c == '\n';
}
static String LimitLength(const String& string, unsigned max_length) {
unsigned new_length = std::min(max_length, string.length());
if (new_length == string.length())
return string;
if (new_length > 0 && U16_IS_LEAD(string[new_length - 1]))
--new_length;
return string.Left(new_length);
}
String TextFieldInputType::SanitizeValue(const String& proposed_value) const {
return LimitLength(proposed_value.RemoveCharacters(IsASCIILineBreak),
std::numeric_limits<int>::max());
}
void TextFieldInputType::HandleBeforeTextInsertedEvent(
BeforeTextInsertedEvent* event) {
// Make sure that the text to be inserted will not violate the maxLength.
// We use HTMLInputElement::innerEditorValue() instead of
// HTMLInputElement::value() because they can be mismatched by
// sanitizeValue() in HTMLInputElement::subtreeHasChanged() in some cases.
unsigned old_length = GetElement().InnerEditorValue().length();
// selectionLength represents the selection length of this text field to be
// removed by this insertion.
// If the text field has no focus, we don't need to take account of the
// selection length. The selection is the source of text drag-and-drop in
// that case, and nothing in the text field will be removed.
unsigned selection_length = 0;
if (GetElement().IsFocused()) {
// TODO(editing-dev): Use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. See http://crbug.com/590369 for more details.
GetElement().GetDocument().UpdateStyleAndLayoutIgnorePendingStylesheets();
selection_length = GetElement()
.GetDocument()
.GetFrame()
->Selection()
.SelectedText()
.length();
}
DCHECK_GE(old_length, selection_length);
// Selected characters will be removed by the next text event.
unsigned base_length = old_length - selection_length;
unsigned max_length;
if (this->MaxLength() < 0)
max_length = std::numeric_limits<int>::max();
else
max_length = static_cast<unsigned>(this->MaxLength());
unsigned appendable_length =
max_length > base_length ? max_length - base_length : 0;
// Truncate the inserted text to avoid violating the maxLength and other
// constraints.
String event_text = event->GetText();
unsigned text_length = event_text.length();
while (text_length > 0 && IsASCIILineBreak(event_text[text_length - 1]))
text_length--;
event_text.Truncate(text_length);
event_text.Replace("\r\n", " ");
event_text.Replace('\r', ' ');
event_text.Replace('\n', ' ');
event->SetText(LimitLength(event_text, appendable_length));
}
bool TextFieldInputType::ShouldRespectListAttribute() {
return true;
}
void TextFieldInputType::UpdatePlaceholderText() {
if (!SupportsPlaceholder())
return;
HTMLElement* placeholder = GetElement().PlaceholderElement();
String placeholder_text = GetElement().GetPlaceholderValue();
if (placeholder_text.IsEmpty()) {
if (placeholder)
placeholder->remove(ASSERT_NO_EXCEPTION);
return;
}
if (!placeholder) {
HTMLElement* new_element =
HTMLDivElement::Create(GetElement().GetDocument());
placeholder = new_element;
placeholder->SetShadowPseudoId(AtomicString("-webkit-input-placeholder"));
placeholder->SetInlineStyleProperty(
CSSPropertyDisplay,
GetElement().IsPlaceholderVisible() ? CSSValueBlock : CSSValueNone,
true);
placeholder->setAttribute(idAttr, ShadowElementNames::Placeholder());
Element* container = ContainerElement();
Node* previous = container ? container : GetElement().InnerEditorElement();
previous->parentNode()->InsertBefore(placeholder, previous);
SECURITY_DCHECK(placeholder->parentNode() == previous->parentNode());
}
placeholder->setTextContent(placeholder_text);
}
void TextFieldInputType::AppendToFormData(FormData& form_data) const {
InputType::AppendToFormData(form_data);
const AtomicString& dirname_attr_value =
GetElement().FastGetAttribute(dirnameAttr);
if (!dirname_attr_value.IsNull())
form_data.append(dirname_attr_value, GetElement().DirectionForFormData());
}
String TextFieldInputType::ConvertFromVisibleValue(
const String& visible_value) const {
return visible_value;
}
void TextFieldInputType::SubtreeHasChanged() {
GetElement().SetValueFromRenderer(SanitizeUserInputValue(
ConvertFromVisibleValue(GetElement().InnerEditorValue())));
GetElement().UpdatePlaceholderVisibility();
GetElement().PseudoStateChanged(CSSSelector::kPseudoValid);
GetElement().PseudoStateChanged(CSSSelector::kPseudoInvalid);
GetElement().PseudoStateChanged(CSSSelector::kPseudoInRange);
GetElement().PseudoStateChanged(CSSSelector::kPseudoOutOfRange);
DidSetValueByUserEdit();
}
void TextFieldInputType::DidSetValueByUserEdit() {
if (!GetElement().IsFocused())
return;
if (ChromeClient* chrome_client = this->GetChromeClient())
chrome_client->DidChangeValueInTextField(GetElement());
}
void TextFieldInputType::SpinButtonStepDown() {
StepUpFromLayoutObject(-1);
}
void TextFieldInputType::SpinButtonStepUp() {
StepUpFromLayoutObject(1);
}
void TextFieldInputType::UpdateView() {
if (GetElement().SuggestedValue().IsEmpty() &&
GetElement().NeedsToUpdateViewValue()) {
// Update the view only if needsToUpdateViewValue is true. It protects
// an unacceptable view value from being overwritten with the DOM value.
//
// e.g. <input type=number> has a view value "abc", and input.max is
// updated. In this case, updateView() is called but we should not
// update the view value.
GetElement().SetInnerEditorValue(VisibleValue());
GetElement().UpdatePlaceholderVisibility();
}
}
void TextFieldInputType::FocusAndSelectSpinButtonOwner() {
GetElement().focus();
GetElement().SetSelectionRange(0, std::numeric_limits<int>::max());
}
bool TextFieldInputType::ShouldSpinButtonRespondToMouseEvents() {
return !GetElement().IsDisabledOrReadOnly();
}
bool TextFieldInputType::ShouldSpinButtonRespondToWheelEvents() {
return ShouldSpinButtonRespondToMouseEvents() && GetElement().IsFocused();
}
void TextFieldInputType::SpinButtonDidReleaseMouseCapture(
SpinButtonElement::EventDispatch event_dispatch) {
if (event_dispatch == SpinButtonElement::kEventDispatchAllowed)
GetElement().DispatchFormControlChangeEvent();
}
} // namespace blink