blob: 41ecf18a4ac2016577e7d2402108fe64f8efc31e [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
* (C) 2006 Alexey Proskuryakov (ap@nypop.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "core/html/TextControlElement.h"
#include "bindings/core/v8/ExceptionMessages.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/HTMLNames.h"
#include "core/dom/AXObjectCache.h"
#include "core/dom/Document.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/Text.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/Editor.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/iterators/CharacterIterator.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/editing/serializers/Serialization.h"
#include "core/events/Event.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLBRElement.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/html/shadow/ShadowElementNames.h"
#include "core/layout/LayoutBlock.h"
#include "core/layout/LayoutBlockFlow.h"
#include "core/layout/LayoutTheme.h"
#include "core/page/FocusController.h"
#include "core/page/Page.h"
#include "platform/heap/Handle.h"
#include "platform/wtf/text/StringBuilder.h"
namespace blink {
using namespace HTMLNames;
TextControlElement::TextControlElement(const QualifiedName& tag_name,
Document& doc)
: HTMLFormControlElementWithState(tag_name, doc),
last_change_was_user_edit_(false),
cached_selection_start_(0),
cached_selection_end_(0) {
cached_selection_direction_ =
doc.GetFrame() && doc.GetFrame()
->GetEditor()
.Behavior()
.ShouldConsiderSelectionAsDirectional()
? kSelectionHasForwardDirection
: kSelectionHasNoDirection;
}
TextControlElement::~TextControlElement() {}
void TextControlElement::DispatchFocusEvent(
Element* old_focused_element,
WebFocusType type,
InputDeviceCapabilities* source_capabilities) {
if (SupportsPlaceholder())
UpdatePlaceholderVisibility();
HandleFocusEvent(old_focused_element, type);
HTMLFormControlElementWithState::DispatchFocusEvent(old_focused_element, type,
source_capabilities);
}
void TextControlElement::DispatchBlurEvent(
Element* new_focused_element,
WebFocusType type,
InputDeviceCapabilities* source_capabilities) {
if (SupportsPlaceholder())
UpdatePlaceholderVisibility();
HandleBlurEvent();
HTMLFormControlElementWithState::DispatchBlurEvent(new_focused_element, type,
source_capabilities);
}
void TextControlElement::DefaultEventHandler(Event* event) {
if (event->type() == EventTypeNames::webkitEditableContentChanged &&
GetLayoutObject() && GetLayoutObject()->IsTextControl()) {
last_change_was_user_edit_ = !GetDocument().IsRunningExecCommand();
if (IsFocused()) {
// Updating the cache in SelectionChanged() isn't enough because
// SelectionChanged() is not called if:
// - Text nodes in the inner-editor is split to multiple, and
// - The caret is on the beginning of a Text node, and its previous node
// is updated, or
// - The caret is on the end of a text node, and its next node is updated.
CacheSelection(ComputeSelectionStart(), ComputeSelectionEnd(),
ComputeSelectionDirection());
}
SubtreeHasChanged();
return;
}
HTMLFormControlElementWithState::DefaultEventHandler(event);
}
void TextControlElement::ForwardEvent(Event* event) {
if (event->type() == EventTypeNames::blur ||
event->type() == EventTypeNames::focus)
return;
InnerEditorElement()->DefaultEventHandler(event);
}
String TextControlElement::StrippedPlaceholder() const {
// According to the HTML5 specification, we need to remove CR and LF from
// the attribute value.
const AtomicString& attribute_value = FastGetAttribute(placeholderAttr);
if (!attribute_value.Contains(kNewlineCharacter) &&
!attribute_value.Contains(kCarriageReturnCharacter))
return attribute_value;
StringBuilder stripped;
unsigned length = attribute_value.length();
stripped.ReserveCapacity(length);
for (unsigned i = 0; i < length; ++i) {
UChar character = attribute_value[i];
if (character == kNewlineCharacter || character == kCarriageReturnCharacter)
continue;
stripped.Append(character);
}
return stripped.ToString();
}
static bool IsNotLineBreak(UChar ch) {
return ch != kNewlineCharacter && ch != kCarriageReturnCharacter;
}
bool TextControlElement::IsPlaceholderEmpty() const {
const AtomicString& attribute_value = FastGetAttribute(placeholderAttr);
return attribute_value.GetString().Find(IsNotLineBreak) == kNotFound;
}
bool TextControlElement::PlaceholderShouldBeVisible() const {
return SupportsPlaceholder() && IsEmptyValue() && IsEmptySuggestedValue() &&
!IsPlaceholderEmpty();
}
HTMLElement* TextControlElement::PlaceholderElement() const {
return ToHTMLElementOrDie(
UserAgentShadowRoot()->GetElementById(ShadowElementNames::Placeholder()));
}
void TextControlElement::UpdatePlaceholderVisibility() {
HTMLElement* placeholder = PlaceholderElement();
if (!placeholder) {
UpdatePlaceholderText();
return;
}
bool place_holder_was_visible = IsPlaceholderVisible();
SetPlaceholderVisibility(PlaceholderShouldBeVisible());
if (place_holder_was_visible == IsPlaceholderVisible())
return;
PseudoStateChanged(CSSSelector::kPseudoPlaceholderShown);
placeholder->SetInlineStyleProperty(
CSSPropertyDisplay, IsPlaceholderVisible() ? CSSValueBlock : CSSValueNone,
true);
}
void TextControlElement::setSelectionStart(unsigned start) {
setSelectionRangeForBinding(start, std::max(start, selectionEnd()),
selectionDirection());
}
void TextControlElement::setSelectionEnd(unsigned end) {
setSelectionRangeForBinding(std::min(end, selectionStart()), end,
selectionDirection());
}
void TextControlElement::setSelectionDirection(const String& direction) {
setSelectionRangeForBinding(selectionStart(), selectionEnd(), direction);
}
void TextControlElement::select() {
setSelectionRangeForBinding(0, std::numeric_limits<unsigned>::max());
// Avoid SelectionBehaviorOnFocus::Restore, which scrolls containers to show
// the selection.
focus(
FocusParams(SelectionBehaviorOnFocus::kNone, kWebFocusTypeNone, nullptr));
RestoreCachedSelection();
}
void TextControlElement::SetValueBeforeFirstUserEditIfNotSet() {
if (!value_before_first_user_edit_.IsNull())
return;
String value = this->value();
value_before_first_user_edit_ = value.IsNull() ? g_empty_string : value;
}
void TextControlElement::CheckIfValueWasReverted(const String& value) {
DCHECK(!value_before_first_user_edit_.IsNull())
<< "setValueBeforeFirstUserEditIfNotSet should be called beforehand.";
String non_null_value = value.IsNull() ? g_empty_string : value;
if (value_before_first_user_edit_ == non_null_value)
ClearValueBeforeFirstUserEdit();
}
void TextControlElement::ClearValueBeforeFirstUserEdit() {
value_before_first_user_edit_ = String();
}
void TextControlElement::SetFocused(bool flag, WebFocusType focus_type) {
HTMLFormControlElementWithState::SetFocused(flag, focus_type);
if (!flag)
DispatchFormControlChangeEvent();
}
void TextControlElement::DispatchFormControlChangeEvent() {
if (!value_before_first_user_edit_.IsNull() &&
!EqualIgnoringNullity(value_before_first_user_edit_, value())) {
ClearValueBeforeFirstUserEdit();
DispatchChangeEvent();
} else {
ClearValueBeforeFirstUserEdit();
}
}
void TextControlElement::EnqueueChangeEvent() {
if (!value_before_first_user_edit_.IsNull() &&
!EqualIgnoringNullity(value_before_first_user_edit_, value())) {
Event* event = Event::CreateBubble(EventTypeNames::change);
event->SetTarget(this);
GetDocument().EnqueueAnimationFrameEvent(event);
}
ClearValueBeforeFirstUserEdit();
}
void TextControlElement::setRangeText(const String& replacement,
ExceptionState& exception_state) {
setRangeText(replacement, selectionStart(), selectionEnd(), "preserve",
exception_state);
}
void TextControlElement::setRangeText(const String& replacement,
unsigned start,
unsigned end,
const String& selection_mode,
ExceptionState& exception_state) {
if (start > end) {
exception_state.ThrowDOMException(
kIndexSizeError, "The provided start value (" + String::Number(start) +
") is larger than the provided end value (" +
String::Number(end) + ").");
return;
}
if (openShadowRoot())
return;
String text = InnerEditorValue();
unsigned text_length = text.length();
unsigned replacement_length = replacement.length();
unsigned new_selection_start = selectionStart();
unsigned new_selection_end = selectionEnd();
start = std::min(start, text_length);
end = std::min(end, text_length);
if (start < end)
text.Replace(start, end - start, replacement);
else
text.insert(replacement, start);
setValue(text, TextFieldEventBehavior::kDispatchNoEvent,
TextControlSetValueSelection::kDoNotSet);
if (selection_mode == "select") {
new_selection_start = start;
new_selection_end = start + replacement_length;
} else if (selection_mode == "start") {
new_selection_start = new_selection_end = start;
} else if (selection_mode == "end") {
new_selection_start = new_selection_end = start + replacement_length;
} else {
DCHECK_EQ(selection_mode, "preserve");
long delta = replacement_length - (end - start);
if (new_selection_start > end)
new_selection_start += delta;
else if (new_selection_start > start)
new_selection_start = start;
if (new_selection_end > end)
new_selection_end += delta;
else if (new_selection_end > start)
new_selection_end = start + replacement_length;
}
setSelectionRangeForBinding(new_selection_start, new_selection_end);
}
void TextControlElement::setSelectionRangeForBinding(
unsigned start,
unsigned end,
const String& direction_string) {
TextFieldSelectionDirection direction = kSelectionHasNoDirection;
if (direction_string == "forward")
direction = kSelectionHasForwardDirection;
else if (direction_string == "backward")
direction = kSelectionHasBackwardDirection;
if (SetSelectionRange(start, end, direction))
ScheduleSelectEvent();
}
static Position PositionForIndex(HTMLElement* inner_editor, unsigned index) {
if (index == 0) {
Node* node = NodeTraversal::Next(*inner_editor, inner_editor);
if (node && node->IsTextNode())
return Position(node, 0);
return Position(inner_editor, 0);
}
unsigned remaining_characters_to_move_forward = index;
Node* last_br_or_text = inner_editor;
for (Node& node : NodeTraversal::DescendantsOf(*inner_editor)) {
if (node.HasTagName(brTag)) {
if (remaining_characters_to_move_forward == 0)
return Position::BeforeNode(&node);
--remaining_characters_to_move_forward;
last_br_or_text = &node;
continue;
}
if (node.IsTextNode()) {
Text& text = ToText(node);
if (remaining_characters_to_move_forward < text.length())
return Position(&text, remaining_characters_to_move_forward);
remaining_characters_to_move_forward -= text.length();
last_br_or_text = &node;
continue;
}
NOTREACHED();
}
return LastPositionInOrAfterNode(last_br_or_text);
}
unsigned TextControlElement::IndexForPosition(HTMLElement* inner_editor,
const Position& passed_position) {
if (!inner_editor || !inner_editor->contains(passed_position.AnchorNode()) ||
passed_position.IsNull())
return 0;
if (Position::BeforeNode(inner_editor) == passed_position)
return 0;
unsigned index = 0;
Node* start_node = passed_position.ComputeNodeBeforePosition();
if (!start_node)
start_node = passed_position.ComputeContainerNode();
if (start_node == inner_editor && passed_position.IsAfterAnchor())
start_node = inner_editor->LastChild();
DCHECK(start_node);
DCHECK(inner_editor->contains(start_node));
for (Node* node = start_node; node;
node = NodeTraversal::Previous(*node, inner_editor)) {
if (node->IsTextNode()) {
int length = ToText(*node).length();
if (node == passed_position.ComputeContainerNode())
index += std::min(length, passed_position.OffsetInContainerNode());
else
index += length;
} else if (node->HasTagName(brTag)) {
++index;
}
}
return index;
}
bool TextControlElement::SetSelectionRange(
unsigned start,
unsigned end,
TextFieldSelectionDirection direction) {
if (openShadowRoot() || !IsTextControl())
return false;
const unsigned editor_value_length = InnerEditorValue().length();
end = std::min(end, editor_value_length);
start = std::min(start, end);
LocalFrame* frame = GetDocument().GetFrame();
if (direction == kSelectionHasNoDirection && frame &&
frame->GetEditor().Behavior().ShouldConsiderSelectionAsDirectional())
direction = kSelectionHasForwardDirection;
bool did_change = CacheSelection(start, end, direction);
if (GetDocument().FocusedElement() != this)
return did_change;
HTMLElement* inner_editor = InnerEditorElement();
if (!frame || !inner_editor)
return did_change;
Position start_position = PositionForIndex(inner_editor, start);
Position end_position =
start == end ? start_position : PositionForIndex(inner_editor, end);
DCHECK_EQ(start, IndexForPosition(inner_editor, start_position));
DCHECK_EQ(end, IndexForPosition(inner_editor, end_position));
#if DCHECK_IS_ON()
// startPosition and endPosition can be null position for example when
// "-webkit-user-select: none" style attribute is specified.
if (start_position.IsNotNull() && end_position.IsNotNull()) {
DCHECK_EQ(start_position.AnchorNode()->OwnerShadowHost(), this);
DCHECK_EQ(end_position.AnchorNode()->OwnerShadowHost(), this);
}
#endif // DCHECK_IS_ON()
frame->Selection().SetSelection(
SelectionInDOMTree::Builder()
.Collapse(direction == kSelectionHasBackwardDirection
? end_position
: start_position)
.Extend(direction == kSelectionHasBackwardDirection ? start_position
: end_position)
.SetIsDirectional(direction != kSelectionHasNoDirection)
.Build(),
FrameSelection::kCloseTyping | FrameSelection::kClearTypingStyle |
FrameSelection::kDoNotSetFocus);
return did_change;
}
bool TextControlElement::CacheSelection(unsigned start,
unsigned end,
TextFieldSelectionDirection direction) {
DCHECK_LE(start, end);
bool did_change = cached_selection_start_ != start ||
cached_selection_end_ != end ||
cached_selection_direction_ != direction;
cached_selection_start_ = start;
cached_selection_end_ = end;
cached_selection_direction_ = direction;
return did_change;
}
VisiblePosition TextControlElement::VisiblePositionForIndex(int index) const {
if (index <= 0)
return VisiblePosition::FirstPositionInNode(InnerEditorElement());
Position start, end;
bool selected = Range::selectNodeContents(InnerEditorElement(), start, end);
if (!selected)
return VisiblePosition();
CharacterIterator it(start, end);
it.Advance(index - 1);
return CreateVisiblePosition(it.EndPosition(), TextAffinity::kUpstream);
}
// TODO(yosin): We should move |TextControlElement::indexForVisiblePosition()|
// to "AXLayoutObject.cpp" since this funciton is used only there.
int TextControlElement::IndexForVisiblePosition(
const VisiblePosition& pos) const {
Position index_position = pos.DeepEquivalent().ParentAnchoredEquivalent();
if (EnclosingTextControl(index_position) != this)
return 0;
DCHECK(index_position.IsConnected()) << index_position;
return TextIterator::RangeLength(Position(InnerEditorElement(), 0),
index_position);
}
unsigned TextControlElement::selectionStart() const {
if (!IsTextControl())
return 0;
if (GetDocument().FocusedElement() != this)
return cached_selection_start_;
return ComputeSelectionStart();
}
unsigned TextControlElement::ComputeSelectionStart() const {
DCHECK(IsTextControl());
LocalFrame* frame = GetDocument().GetFrame();
if (!frame)
return 0;
{
// To avoid regression on speedometer benchmark[1] test, we should not
// update layout tree in this code block.
// [1] http://browserbench.org/Speedometer/
DocumentLifecycle::DisallowTransitionScope disallow_transition(
GetDocument().Lifecycle());
const SelectionInDOMTree& selection =
frame->Selection().GetSelectionInDOMTree();
if (selection.Granularity() == kCharacterGranularity) {
return IndexForPosition(InnerEditorElement(),
selection.ComputeStartPosition());
}
}
const VisibleSelection& visible_selection =
frame->Selection().ComputeVisibleSelectionInDOMTreeDeprecated();
return IndexForPosition(InnerEditorElement(), visible_selection.Start());
}
unsigned TextControlElement::selectionEnd() const {
if (!IsTextControl())
return 0;
if (GetDocument().FocusedElement() != this)
return cached_selection_end_;
return ComputeSelectionEnd();
}
unsigned TextControlElement::ComputeSelectionEnd() const {
DCHECK(IsTextControl());
LocalFrame* frame = GetDocument().GetFrame();
if (!frame)
return 0;
{
// To avoid regression on speedometer benchmark[1] test, we should not
// update layout tree in this code block.
// [1] http://browserbench.org/Speedometer/
DocumentLifecycle::DisallowTransitionScope disallow_transition(
GetDocument().Lifecycle());
const SelectionInDOMTree& selection =
frame->Selection().GetSelectionInDOMTree();
if (selection.Granularity() == kCharacterGranularity) {
return IndexForPosition(InnerEditorElement(),
selection.ComputeEndPosition());
}
}
const VisibleSelection& visible_selection =
frame->Selection().ComputeVisibleSelectionInDOMTreeDeprecated();
return IndexForPosition(InnerEditorElement(), visible_selection.end());
}
static const AtomicString& DirectionString(
TextFieldSelectionDirection direction) {
DEFINE_STATIC_LOCAL(const AtomicString, none, ("none"));
DEFINE_STATIC_LOCAL(const AtomicString, forward, ("forward"));
DEFINE_STATIC_LOCAL(const AtomicString, backward, ("backward"));
switch (direction) {
case kSelectionHasNoDirection:
return none;
case kSelectionHasForwardDirection:
return forward;
case kSelectionHasBackwardDirection:
return backward;
}
NOTREACHED();
return none;
}
const AtomicString& TextControlElement::selectionDirection() const {
// Ensured by HTMLInputElement::selectionDirectionForBinding().
DCHECK(IsTextControl());
if (GetDocument().FocusedElement() != this)
return DirectionString(cached_selection_direction_);
return DirectionString(ComputeSelectionDirection());
}
TextFieldSelectionDirection TextControlElement::ComputeSelectionDirection()
const {
DCHECK(IsTextControl());
LocalFrame* frame = GetDocument().GetFrame();
if (!frame)
return kSelectionHasNoDirection;
// To avoid regression on speedometer benchmark[1] test, we should not
// update layout tree in this code block.
// [1] http://browserbench.org/Speedometer/
DocumentLifecycle::DisallowTransitionScope disallow_transition(
GetDocument().Lifecycle());
const SelectionInDOMTree& selection =
frame->Selection().GetSelectionInDOMTree();
const Position& start = selection.ComputeStartPosition();
return selection.IsDirectional()
? (selection.Base() == start ? kSelectionHasForwardDirection
: kSelectionHasBackwardDirection)
: kSelectionHasNoDirection;
}
static inline void SetContainerAndOffsetForRange(Node* node,
int offset,
Node*& container_node,
int& offset_in_container) {
if (node->IsTextNode()) {
container_node = node;
offset_in_container = offset;
} else {
container_node = node->parentNode();
offset_in_container = node->NodeIndex() + offset;
}
}
SelectionInDOMTree TextControlElement::Selection() const {
if (!GetLayoutObject() || !IsTextControl())
return SelectionInDOMTree();
int start = cached_selection_start_;
int end = cached_selection_end_;
DCHECK_LE(start, end);
HTMLElement* inner_text = InnerEditorElement();
if (!inner_text)
return SelectionInDOMTree();
if (!inner_text->HasChildren()) {
return SelectionInDOMTree::Builder()
.Collapse(Position(inner_text, 0))
.SetIsDirectional(selectionDirection() != "none")
.Build();
}
int offset = 0;
Node* start_node = 0;
Node* end_node = 0;
for (Node& node : NodeTraversal::DescendantsOf(*inner_text)) {
DCHECK(!node.hasChildren());
DCHECK(node.IsTextNode() || isHTMLBRElement(node));
int length = node.IsTextNode() ? Position::LastOffsetInNode(&node) : 1;
if (offset <= start && start <= offset + length)
SetContainerAndOffsetForRange(&node, start - offset, start_node, start);
if (offset <= end && end <= offset + length) {
SetContainerAndOffsetForRange(&node, end - offset, end_node, end);
break;
}
offset += length;
}
if (!start_node || !end_node)
return SelectionInDOMTree();
return SelectionInDOMTree::Builder()
.SetBaseAndExtent(Position(start_node, start), Position(end_node, end))
.SetIsDirectional(selectionDirection() != "none")
.Build();
}
const AtomicString& TextControlElement::autocapitalize() const {
DEFINE_STATIC_LOCAL(const AtomicString, off, ("off"));
DEFINE_STATIC_LOCAL(const AtomicString, none, ("none"));
DEFINE_STATIC_LOCAL(const AtomicString, characters, ("characters"));
DEFINE_STATIC_LOCAL(const AtomicString, words, ("words"));
DEFINE_STATIC_LOCAL(const AtomicString, sentences, ("sentences"));
const AtomicString& value = FastGetAttribute(autocapitalizeAttr);
if (DeprecatedEqualIgnoringCase(value, none) ||
DeprecatedEqualIgnoringCase(value, off))
return none;
if (DeprecatedEqualIgnoringCase(value, characters))
return characters;
if (DeprecatedEqualIgnoringCase(value, words))
return words;
if (DeprecatedEqualIgnoringCase(value, sentences))
return sentences;
// Invalid or missing value.
return DefaultAutocapitalize();
}
void TextControlElement::setAutocapitalize(const AtomicString& autocapitalize) {
setAttribute(autocapitalizeAttr, autocapitalize);
}
int TextControlElement::maxLength() const {
int value;
if (!ParseHTMLInteger(FastGetAttribute(maxlengthAttr), value))
return -1;
return value >= 0 ? value : -1;
}
int TextControlElement::minLength() const {
int value;
if (!ParseHTMLInteger(FastGetAttribute(minlengthAttr), value))
return -1;
return value >= 0 ? value : -1;
}
void TextControlElement::setMaxLength(int new_value,
ExceptionState& exception_state) {
int min = minLength();
if (new_value < 0) {
exception_state.ThrowDOMException(
kIndexSizeError, "The value provided (" + String::Number(new_value) +
") is not positive or 0.");
} else if (min >= 0 && new_value < min) {
exception_state.ThrowDOMException(
kIndexSizeError, ExceptionMessages::IndexExceedsMinimumBound(
"maxLength", new_value, min));
} else {
SetIntegralAttribute(maxlengthAttr, new_value);
}
}
void TextControlElement::setMinLength(int new_value,
ExceptionState& exception_state) {
int max = maxLength();
if (new_value < 0) {
exception_state.ThrowDOMException(
kIndexSizeError, "The value provided (" + String::Number(new_value) +
") is not positive or 0.");
} else if (max >= 0 && new_value > max) {
exception_state.ThrowDOMException(
kIndexSizeError, ExceptionMessages::IndexExceedsMaximumBound(
"minLength", new_value, max));
} else {
SetIntegralAttribute(minlengthAttr, new_value);
}
}
void TextControlElement::RestoreCachedSelection() {
if (SetSelectionRange(cached_selection_start_, cached_selection_end_,
cached_selection_direction_))
ScheduleSelectEvent();
}
void TextControlElement::SelectionChanged(bool user_triggered) {
if (!GetLayoutObject() || !IsTextControl())
return;
// selectionStart() or selectionEnd() will return cached selection when this
// node doesn't have focus.
CacheSelection(ComputeSelectionStart(), ComputeSelectionEnd(),
ComputeSelectionDirection());
LocalFrame* frame = GetDocument().GetFrame();
if (!frame || !user_triggered)
return;
const SelectionInDOMTree& selection =
frame->Selection().GetSelectionInDOMTree();
if (selection.SelectionTypeWithLegacyGranularity() != kRangeSelection)
return;
DispatchEvent(Event::CreateBubble(EventTypeNames::select));
}
void TextControlElement::ScheduleSelectEvent() {
Event* event = Event::CreateBubble(EventTypeNames::select);
event->SetTarget(this);
GetDocument().EnqueueAnimationFrameEvent(event);
}
void TextControlElement::ParseAttribute(
const AttributeModificationParams& params) {
if (params.name == autocapitalizeAttr)
UseCounter::Count(GetDocument(), UseCounter::kAutocapitalizeAttribute);
if (params.name == placeholderAttr) {
UpdatePlaceholderText();
UpdatePlaceholderVisibility();
UseCounter::Count(GetDocument(), UseCounter::kPlaceholderAttribute);
} else {
HTMLFormControlElementWithState::ParseAttribute(params);
}
}
bool TextControlElement::LastChangeWasUserEdit() const {
if (!IsTextControl())
return false;
return last_change_was_user_edit_;
}
Node* TextControlElement::CreatePlaceholderBreakElement() const {
return HTMLBRElement::Create(GetDocument());
}
void TextControlElement::AddPlaceholderBreakElementIfNecessary() {
HTMLElement* inner_editor = InnerEditorElement();
if (inner_editor->GetLayoutObject() &&
!inner_editor->GetLayoutObject()->Style()->PreserveNewline())
return;
Node* last_child = inner_editor->LastChild();
if (!last_child || !last_child->IsTextNode())
return;
if (ToText(last_child)->data().EndsWith('\n') ||
ToText(last_child)->data().EndsWith('\r'))
inner_editor->AppendChild(CreatePlaceholderBreakElement());
}
void TextControlElement::SetInnerEditorValue(const String& value) {
DCHECK(!openShadowRoot());
if (!IsTextControl() || openShadowRoot())
return;
bool text_is_changed = value != InnerEditorValue();
HTMLElement* inner_editor = InnerEditorElement();
if (!text_is_changed && inner_editor->HasChildren())
return;
// If the last child is a trailing <br> that's appended below, remove it
// first so as to enable setInnerText() fast path of updating a text node.
if (isHTMLBRElement(inner_editor->LastChild()))
inner_editor->RemoveChild(inner_editor->LastChild(), ASSERT_NO_EXCEPTION);
// We don't use setTextContent. It triggers unnecessary paint.
if (value.IsEmpty())
inner_editor->RemoveChildren();
else
ReplaceChildrenWithText(inner_editor, value, ASSERT_NO_EXCEPTION);
// Add <br> so that we can put the caret at the next line of the last
// newline.
AddPlaceholderBreakElementIfNecessary();
if (text_is_changed && GetLayoutObject()) {
if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
cache->HandleTextFormControlChanged(this);
}
}
String TextControlElement::InnerEditorValue() const {
DCHECK(!openShadowRoot());
HTMLElement* inner_editor = InnerEditorElement();
if (!inner_editor || !IsTextControl())
return g_empty_string;
// Typically, innerEditor has 0 or one Text node followed by 0 or one <br>.
if (!inner_editor->HasChildren())
return g_empty_string;
Node& first_child = *inner_editor->FirstChild();
if (first_child.IsTextNode()) {
Node* second_child = first_child.nextSibling();
if (!second_child)
return ToText(first_child).data();
if (!second_child->nextSibling() && isHTMLBRElement(*second_child))
return ToText(first_child).data();
} else if (!first_child.nextSibling() && isHTMLBRElement(first_child)) {
return g_empty_string;
}
StringBuilder result;
for (Node& node : NodeTraversal::InclusiveDescendantsOf(*inner_editor)) {
if (isHTMLBRElement(node)) {
DCHECK_EQ(&node, inner_editor->LastChild());
if (&node != inner_editor->LastChild())
result.Append(kNewlineCharacter);
} else if (node.IsTextNode()) {
result.Append(ToText(node).data());
}
}
return result.ToString();
}
static void GetNextSoftBreak(RootInlineBox*& line,
Node*& break_node,
unsigned& break_offset) {
RootInlineBox* next;
for (; line; line = next) {
next = line->NextRootBox();
if (next && !line->EndsWithBreak()) {
DCHECK(line->LineBreakObj());
break_node = line->LineBreakObj().GetNode();
break_offset = line->LineBreakPos();
line = next;
return;
}
}
break_node = 0;
break_offset = 0;
}
String TextControlElement::ValueWithHardLineBreaks() const {
// FIXME: It's not acceptable to ignore the HardWrap setting when there is no
// layoutObject. While we have no evidence this has ever been a practical
// problem, it would be best to fix it some day.
HTMLElement* inner_text = InnerEditorElement();
if (!inner_text || !IsTextControl())
return value();
LayoutBlockFlow* layout_object =
ToLayoutBlockFlow(inner_text->GetLayoutObject());
if (!layout_object)
return value();
Node* break_node;
unsigned break_offset;
RootInlineBox* line = layout_object->FirstRootBox();
if (!line)
return value();
GetNextSoftBreak(line, break_node, break_offset);
StringBuilder result;
for (Node& node : NodeTraversal::DescendantsOf(*inner_text)) {
if (isHTMLBRElement(node)) {
DCHECK_EQ(&node, inner_text->LastChild());
if (&node != inner_text->LastChild())
result.Append(kNewlineCharacter);
} else if (node.IsTextNode()) {
String data = ToText(node).data();
unsigned length = data.length();
unsigned position = 0;
while (break_node == node && break_offset <= length) {
if (break_offset > position) {
result.Append(data, position, break_offset - position);
position = break_offset;
result.Append(kNewlineCharacter);
}
GetNextSoftBreak(line, break_node, break_offset);
}
result.Append(data, position, length - position);
}
while (break_node == node)
GetNextSoftBreak(line, break_node, break_offset);
}
return result.ToString();
}
TextControlElement* EnclosingTextControl(const Position& position) {
DCHECK(position.IsNull() || position.IsOffsetInAnchor() ||
position.ComputeContainerNode() ||
!position.AnchorNode()->OwnerShadowHost() ||
(position.AnchorNode()->parentNode() &&
position.AnchorNode()->parentNode()->IsShadowRoot()));
return EnclosingTextControl(position.ComputeContainerNode());
}
TextControlElement* EnclosingTextControl(const Node* container) {
if (!container)
return nullptr;
Element* ancestor = container->OwnerShadowHost();
return ancestor && IsTextControlElement(*ancestor) &&
container->ContainingShadowRoot()->GetType() ==
ShadowRootType::kUserAgent
? ToTextControlElement(ancestor)
: 0;
}
String TextControlElement::DirectionForFormData() const {
for (const HTMLElement* element = this; element;
element = Traversal<HTMLElement>::FirstAncestor(*element)) {
const AtomicString& dir_attribute_value =
element->FastGetAttribute(dirAttr);
if (dir_attribute_value.IsNull())
continue;
if (DeprecatedEqualIgnoringCase(dir_attribute_value, "rtl") ||
DeprecatedEqualIgnoringCase(dir_attribute_value, "ltr"))
return dir_attribute_value;
if (DeprecatedEqualIgnoringCase(dir_attribute_value, "auto")) {
bool is_auto;
TextDirection text_direction =
element->DirectionalityIfhasDirAutoAttribute(is_auto);
return text_direction == TextDirection::kRtl ? "rtl" : "ltr";
}
}
return "ltr";
}
HTMLElement* TextControlElement::InnerEditorElement() const {
return ToHTMLElementOrDie(
UserAgentShadowRoot()->GetElementById(ShadowElementNames::InnerEditor()));
}
void TextControlElement::CopyNonAttributePropertiesFromElement(
const Element& source) {
const TextControlElement& source_element =
static_cast<const TextControlElement&>(source);
last_change_was_user_edit_ = source_element.last_change_was_user_edit_;
HTMLFormControlElement::CopyNonAttributePropertiesFromElement(source);
}
} // namespace blink