blob: 0d857bc97f229c22b9dd28aee749ab5efd5f618e [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, 2008, 2010 Apple Inc. All rights
* reserved.
* (C) 2006 Alexey Proskuryakov (ap@nypop.com)
* Copyright (C) 2007 Samuel Weinig (sam@webkit.org)
*
* 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 "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
#include "third_party/blink/public/strings/grit/blink_strings.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_focus_options.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
#include "third_party/blink/renderer/core/css_value_keywords.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/events/simulated_click_options.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
#include "third_party/blink/renderer/core/event_interface_names.h"
#include "third_party/blink/renderer/core/events/before_text_inserted_event.h"
#include "third_party/blink/renderer/core/events/drag_event.h"
#include "third_party/blink/renderer/core/events/mouse_event.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/forms/form_controller.h"
#include "third_party/blink/renderer/core/html/forms/form_data.h"
#include "third_party/blink/renderer/core/html/forms/text_control_inner_elements.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html/shadow/shadow_element_names.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_object_factory.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/text/platform_locale.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
namespace blink {
static const unsigned kDefaultRows = 2;
static const unsigned kDefaultCols = 20;
static inline unsigned ComputeLengthForAPIValue(const String& text) {
unsigned length = text.length();
unsigned crlf_count = 0;
for (unsigned i = 0; i < length; ++i) {
if (text[i] == '\r' && i + 1 < length && text[i + 1] == '\n')
crlf_count++;
}
return text.length() - crlf_count;
}
static inline void ReplaceCRWithNewLine(String& text) {
text.Replace("\r\n", "\n");
text.Replace('\r', '\n');
}
HTMLTextAreaElement::HTMLTextAreaElement(Document& document)
: TextControlElement(html_names::kTextareaTag, document),
rows_(kDefaultRows),
cols_(kDefaultCols),
wrap_(kSoftWrap),
is_dirty_(false),
is_placeholder_visible_(false) {
EnsureUserAgentShadowRoot();
}
void HTMLTextAreaElement::DidAddUserAgentShadowRoot(ShadowRoot& root) {
root.AppendChild(CreateInnerEditorElement());
}
const AtomicString& HTMLTextAreaElement::FormControlType() const {
DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea"));
return textarea;
}
FormControlState HTMLTextAreaElement::SaveFormControlState() const {
return is_dirty_ ? FormControlState(value()) : FormControlState();
}
void HTMLTextAreaElement::RestoreFormControlState(
const FormControlState& state) {
setValue(state[0]);
}
int HTMLTextAreaElement::scrollWidth() {
if (SuggestedValue().IsEmpty())
return TextControlElement::scrollWidth();
// If in preview state, fake the scroll width to prevent that any information
// about the suggested content can be derived from the size.
GetDocument().UpdateStyleAndLayoutForNode(this,
DocumentUpdateReason::kJavaScript);
auto* editor = InnerEditorElement();
auto* editor_box = editor ? editor->GetLayoutBox() : nullptr;
auto* box = GetLayoutBox();
if (!box || !editor_box)
return TextControlElement::scrollWidth();
LayoutUnit width =
editor_box->ClientWidth() + box->PaddingLeft() + box->PaddingRight();
return AdjustForAbsoluteZoom::AdjustLayoutUnit(width, box->StyleRef())
.Round();
}
int HTMLTextAreaElement::scrollHeight() {
if (SuggestedValue().IsEmpty())
return TextControlElement::scrollHeight();
// If in preview state, fake the scroll height to prevent that any
// information about the suggested content can be derived from the size.
GetDocument().UpdateStyleAndLayoutForNode(this,
DocumentUpdateReason::kJavaScript);
auto* editor = InnerEditorElement();
auto* editor_box = editor ? editor->GetLayoutBox() : nullptr;
auto* box = GetLayoutBox();
if (!box || !editor_box)
return TextControlElement::scrollHeight();
LayoutUnit height =
editor_box->ClientHeight() + box->PaddingTop() + box->PaddingBottom();
return AdjustForAbsoluteZoom::AdjustLayoutUnit(height, box->StyleRef())
.Round();
}
void HTMLTextAreaElement::ChildrenChanged(const ChildrenChange& change) {
HTMLElement::ChildrenChanged(change);
SetLastChangeWasNotUserEdit();
if (is_dirty_)
SetInnerEditorValue(value());
else
SetNonDirtyValue(defaultValue(), TextControlSetValueSelection::kClamp);
}
bool HTMLTextAreaElement::IsPresentationAttribute(
const QualifiedName& name) const {
if (name == html_names::kAlignAttr) {
// Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
// See http://bugs.webkit.org/show_bug.cgi?id=7075
return false;
}
if (name == html_names::kWrapAttr)
return true;
return TextControlElement::IsPresentationAttribute(name);
}
void HTMLTextAreaElement::CollectStyleForPresentationAttribute(
const QualifiedName& name,
const AtomicString& value,
MutableCSSPropertyValueSet* style) {
if (name == html_names::kWrapAttr) {
if (ShouldWrapText()) {
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kWhiteSpace,
CSSValueID::kPreWrap);
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kOverflowWrap, CSSValueID::kBreakWord);
} else {
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kWhiteSpace,
CSSValueID::kPre);
AddPropertyToPresentationAttributeStyle(
style, CSSPropertyID::kOverflowWrap, CSSValueID::kNormal);
}
} else {
TextControlElement::CollectStyleForPresentationAttribute(name, value,
style);
}
}
void HTMLTextAreaElement::ParseAttribute(
const AttributeModificationParams& params) {
const QualifiedName& name = params.name;
const AtomicString& value = params.new_value;
if (name == html_names::kRowsAttr) {
unsigned rows = 0;
if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, rows) ||
rows <= 0 || rows > 0x7fffffffu)
rows = kDefaultRows;
if (rows_ != rows) {
rows_ = rows;
if (GetLayoutObject()) {
GetLayoutObject()
->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
layout_invalidation_reason::kAttributeChanged);
}
}
} else if (name == html_names::kColsAttr) {
unsigned cols = 0;
if (value.IsEmpty() || !ParseHTMLNonNegativeInteger(value, cols) ||
cols <= 0 || cols > 0x7fffffffu)
cols = kDefaultCols;
if (cols_ != cols) {
cols_ = cols;
if (LayoutObject* layout_object = GetLayoutObject()) {
layout_object
->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
layout_invalidation_reason::kAttributeChanged);
}
}
} else if (name == html_names::kWrapAttr) {
// The virtual/physical values were a Netscape extension of HTML 3.0, now
// deprecated. The soft/hard /off values are a recommendation for HTML 4
// extension by IE and NS 4.
WrapMethod wrap;
if (EqualIgnoringASCIICase(value, "physical") ||
EqualIgnoringASCIICase(value, "hard") ||
EqualIgnoringASCIICase(value, "on"))
wrap = kHardWrap;
else if (EqualIgnoringASCIICase(value, "off"))
wrap = kNoWrap;
else
wrap = kSoftWrap;
if (wrap != wrap_) {
wrap_ = wrap;
if (LayoutObject* layout_object = GetLayoutObject()) {
layout_object
->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
layout_invalidation_reason::kAttributeChanged);
}
}
} else if (name == html_names::kAccesskeyAttr) {
// ignore for the moment
} else if (name == html_names::kMaxlengthAttr) {
UseCounter::Count(GetDocument(), WebFeature::kTextAreaMaxLength);
SetNeedsValidityCheck();
} else if (name == html_names::kMinlengthAttr) {
UseCounter::Count(GetDocument(), WebFeature::kTextAreaMinLength);
SetNeedsValidityCheck();
} else {
TextControlElement::ParseAttribute(params);
}
}
bool HTMLTextAreaElement::TypeShouldForceLegacyLayout() const {
if (RuntimeEnabledFeatures::LayoutNGTextControlEnabled())
return false;
UseCounter::Count(GetDocument(), WebFeature::kLegacyLayoutByTextControl);
return true;
}
LayoutObject* HTMLTextAreaElement::CreateLayoutObject(
const ComputedStyle& style,
LegacyLayout legacy) {
return LayoutObjectFactory::CreateTextControlMultiLine(*this, style, legacy);
}
void HTMLTextAreaElement::AppendToFormData(FormData& form_data) {
if (GetName().IsEmpty())
return;
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kForm);
const String& text =
(wrap_ == kHardWrap) ? ValueWithHardLineBreaks() : value();
form_data.AppendFromElement(GetName(), text);
const AtomicString& dirname_attr_value =
FastGetAttribute(html_names::kDirnameAttr);
if (!dirname_attr_value.IsNull())
form_data.AppendFromElement(dirname_attr_value, DirectionForFormData());
}
void HTMLTextAreaElement::ResetImpl() {
SetNonDirtyValue(defaultValue(),
TextControlSetValueSelection::kSetSelectionToEnd);
}
bool HTMLTextAreaElement::HasCustomFocusLogic() const {
return true;
}
bool HTMLTextAreaElement::IsKeyboardFocusable() const {
// If a given text area can be focused at all, then it will always be keyboard
// focusable.
return IsFocusable();
}
bool HTMLTextAreaElement::MayTriggerVirtualKeyboard() const {
return true;
}
void HTMLTextAreaElement::UpdateFocusAppearanceWithOptions(
SelectionBehaviorOnFocus selection_behavior,
const FocusOptions* options) {
switch (selection_behavior) {
case SelectionBehaviorOnFocus::kReset: // Fallthrough.
case SelectionBehaviorOnFocus::kRestore:
RestoreCachedSelection();
break;
case SelectionBehaviorOnFocus::kNone:
return;
}
if (!options->preventScroll()) {
if (GetDocument().GetFrame())
GetDocument().GetFrame()->Selection().RevealSelection();
}
}
void HTMLTextAreaElement::DefaultEventHandler(Event& event) {
if (GetLayoutObject() &&
(IsA<MouseEvent>(event) || IsA<DragEvent>(event) ||
event.HasInterface(event_interface_names::kWheelEvent) ||
event.type() == event_type_names::kBlur)) {
ForwardEvent(event);
} else if (GetLayoutObject() && event.IsBeforeTextInsertedEvent()) {
HandleBeforeTextInsertedEvent(
static_cast<BeforeTextInsertedEvent*>(&event));
}
TextControlElement::DefaultEventHandler(event);
}
void HTMLTextAreaElement::SubtreeHasChanged() {
#if DCHECK_IS_ON()
// The innerEditor should have either Text nodes or a placeholder break
// element. If we see other nodes, it's a bug in editing code and we should
// fix it.
Element* inner_editor = InnerEditorElement();
for (Node& node : NodeTraversal::DescendantsOf(*inner_editor)) {
if (node.IsTextNode())
continue;
DCHECK(IsA<HTMLBRElement>(node));
DCHECK_EQ(&node, inner_editor->lastChild());
}
#endif
AddPlaceholderBreakElementIfNecessary();
SetValueBeforeFirstUserEditIfNotSet();
UpdateValue();
CheckIfValueWasReverted(value());
SetNeedsValidityCheck();
SetAutofillState(WebAutofillState::kNotFilled);
UpdatePlaceholderVisibility();
if (!IsFocused())
return;
// When typing in a textarea, childrenChanged is not called, so we need to
// force the directionality check.
CalculateAndAdjustAutoDirectionality(this);
DCHECK(GetDocument().IsActive());
GetDocument().GetPage()->GetChromeClient().DidChangeValueInTextField(*this);
}
void HTMLTextAreaElement::HandleBeforeTextInsertedEvent(
BeforeTextInsertedEvent* event) const {
DCHECK(event);
DCHECK(GetLayoutObject());
int signed_max_length = maxLength();
if (signed_max_length < 0)
return;
unsigned unsigned_max_length = static_cast<unsigned>(signed_max_length);
const String& current_value = InnerEditorValue();
unsigned current_length = ComputeLengthForAPIValue(current_value);
if (current_length + ComputeLengthForAPIValue(event->GetText()) <
unsigned_max_length)
return;
// 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 (IsFocused()) {
// TODO(editing-dev): Use of UpdateStyleAndLayout
// needs to be audited. See http://crbug.com/590369 for more details.
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kForm);
selection_length = ComputeLengthForAPIValue(
GetDocument().GetFrame()->Selection().SelectedText());
}
DCHECK_GE(current_length, selection_length);
unsigned base_length = current_length - selection_length;
unsigned appendable_length =
unsigned_max_length > base_length ? unsigned_max_length - base_length : 0;
event->SetText(SanitizeUserInputValue(event->GetText(), appendable_length));
}
String HTMLTextAreaElement::SanitizeUserInputValue(const String& proposed_value,
unsigned max_length) {
unsigned submission_length = 0;
unsigned i = 0;
for (; i < proposed_value.length(); ++i) {
if (proposed_value[i] == '\r' && i + 1 < proposed_value.length() &&
proposed_value[i + 1] == '\n')
continue;
++submission_length;
if (submission_length == max_length) {
++i;
break;
}
if (submission_length > max_length)
break;
}
if (i > 0 && U16_IS_LEAD(proposed_value[i - 1]))
--i;
return proposed_value.Left(i);
}
void HTMLTextAreaElement::UpdateValue() {
value_ = InnerEditorValue();
NotifyFormStateChanged();
is_dirty_ = true;
UpdatePlaceholderVisibility();
}
String HTMLTextAreaElement::value() const {
return value_;
}
void HTMLTextAreaElement::setValue(const String& value,
TextFieldEventBehavior event_behavior,
TextControlSetValueSelection selection) {
SetValueCommon(value, event_behavior, selection);
is_dirty_ = true;
}
void HTMLTextAreaElement::SetNonDirtyValue(
const String& value,
TextControlSetValueSelection selection) {
SetValueCommon(value, TextFieldEventBehavior::kDispatchNoEvent, selection);
is_dirty_ = false;
}
void HTMLTextAreaElement::SetValueCommon(
const String& new_value,
TextFieldEventBehavior event_behavior,
TextControlSetValueSelection selection) {
// Code elsewhere normalizes line endings added by the user via the keyboard
// or pasting. We normalize line endings coming from JavaScript here.
String normalized_value = new_value;
ReplaceCRWithNewLine(normalized_value);
// Clear the suggested value. Use the base class version to not trigger a view
// update.
TextControlElement::SetSuggestedValue(String());
// Return early because we don't want to trigger other side effects when the
// value isn't changing. This is interoperable.
if (normalized_value == value())
return;
// selectionStart and selectionEnd values can be changed by
// SetInnerEditorValue(). We need to get them before SetInnerEditorValue() to
// clamp them later in a case of kClamp.
const bool is_clamp = selection == TextControlSetValueSelection::kClamp;
const unsigned selection_start = is_clamp ? selectionStart() : 0;
const unsigned selection_end = is_clamp ? selectionEnd() : 0;
if (event_behavior != TextFieldEventBehavior::kDispatchNoEvent)
SetValueBeforeFirstUserEditIfNotSet();
value_ = normalized_value;
SetInnerEditorValue(value_);
if (event_behavior == TextFieldEventBehavior::kDispatchNoEvent)
SetLastChangeWasNotUserEdit();
else
CheckIfValueWasReverted(value_);
UpdatePlaceholderVisibility();
SetNeedsStyleRecalc(
kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(style_change_reason::kControlValue));
SetNeedsValidityCheck();
if (selection == TextControlSetValueSelection::kSetSelectionToEnd) {
// Set the caret to the end of the text value except for initialize.
unsigned end_of_string = value_.length();
SetSelectionRange(end_of_string, end_of_string);
} else if (is_clamp) {
const unsigned end_of_string = value_.length();
SetSelectionRange(std::min(end_of_string, selection_start),
std::min(end_of_string, selection_end));
}
NotifyFormStateChanged();
switch (event_behavior) {
case TextFieldEventBehavior::kDispatchChangeEvent:
DispatchFormControlChangeEvent();
break;
case TextFieldEventBehavior::kDispatchInputEvent:
DispatchInputEvent();
break;
case TextFieldEventBehavior::kDispatchInputAndChangeEvent:
DispatchInputEvent();
DispatchFormControlChangeEvent();
break;
case TextFieldEventBehavior::kDispatchNoEvent:
break;
}
}
String HTMLTextAreaElement::defaultValue() const {
StringBuilder value;
// Since there may be comments, ignore nodes other than text nodes.
for (Node* n = firstChild(); n; n = n->nextSibling()) {
if (auto* text_node = DynamicTo<Text>(n))
value.Append(text_node->data());
}
return value.ToString();
}
void HTMLTextAreaElement::setDefaultValue(const String& default_value) {
setTextContent(default_value);
}
void HTMLTextAreaElement::SetSuggestedValue(const String& value) {
TextControlElement::SetSuggestedValue(value);
SetNeedsStyleRecalc(
kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(style_change_reason::kControlValue));
}
String HTMLTextAreaElement::validationMessage() const {
if (!willValidate())
return String();
if (CustomError())
return CustomValidationMessage();
if (ValueMissing())
return GetLocale().QueryString(IDS_FORM_VALIDATION_VALUE_MISSING);
if (TooLong()) {
return GetLocale().ValidationMessageTooLongText(value().length(),
maxLength());
}
if (TooShort()) {
return GetLocale().ValidationMessageTooShortText(value().length(),
minLength());
}
return String();
}
bool HTMLTextAreaElement::ValueMissing() const {
// We should not call value() for performance.
return ValueMissing(nullptr);
}
bool HTMLTextAreaElement::ValueMissing(const String* value) const {
// For textarea elements, the value is missing only if it is mutable.
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-required
return IsRequiredFormControl() && !IsDisabledOrReadOnly() &&
(value ? *value : this->value()).IsEmpty();
}
bool HTMLTextAreaElement::TooLong() const {
// We should not call value() for performance.
return willValidate() && TooLong(nullptr, kCheckDirtyFlag);
}
bool HTMLTextAreaElement::TooShort() const {
// We should not call value() for performance.
return willValidate() && TooShort(nullptr, kCheckDirtyFlag);
}
bool HTMLTextAreaElement::TooLong(const String* value,
NeedsToCheckDirtyFlag check) const {
// Return false for the default value or value set by script even if it is
// longer than maxLength.
if (check == kCheckDirtyFlag && !LastChangeWasUserEdit())
return false;
int max = maxLength();
if (max < 0)
return false;
unsigned len =
value ? ComputeLengthForAPIValue(*value) : this->value().length();
return len > static_cast<unsigned>(max);
}
bool HTMLTextAreaElement::TooShort(const String* value,
NeedsToCheckDirtyFlag check) const {
// Return false for the default value or value set by script even if it is
// shorter than minLength.
if (check == kCheckDirtyFlag && !LastChangeWasUserEdit())
return false;
int min = minLength();
if (min <= 0)
return false;
// An empty string is excluded from minlength check.
unsigned len =
value ? ComputeLengthForAPIValue(*value) : this->value().length();
return len > 0 && len < static_cast<unsigned>(min);
}
bool HTMLTextAreaElement::IsValidValue(const String& candidate) const {
return !ValueMissing(&candidate) && !TooLong(&candidate, kIgnoreDirtyFlag) &&
!TooShort(&candidate, kIgnoreDirtyFlag);
}
void HTMLTextAreaElement::AccessKeyAction(SimulatedClickCreationScope) {
focus();
}
void HTMLTextAreaElement::setCols(unsigned cols) {
SetUnsignedIntegralAttribute(html_names::kColsAttr,
cols ? cols : kDefaultCols, kDefaultCols);
}
void HTMLTextAreaElement::setRows(unsigned rows) {
SetUnsignedIntegralAttribute(html_names::kRowsAttr,
rows ? rows : kDefaultRows, kDefaultRows);
}
bool HTMLTextAreaElement::MatchesReadOnlyPseudoClass() const {
return IsReadOnly();
}
bool HTMLTextAreaElement::MatchesReadWritePseudoClass() const {
return !IsReadOnly();
}
void HTMLTextAreaElement::SetPlaceholderVisibility(bool visible) {
is_placeholder_visible_ = visible;
}
void HTMLTextAreaElement::UpdatePlaceholderText() {
HTMLElement* placeholder = PlaceholderElement();
const String placeholder_text = GetPlaceholderValue();
const bool is_suggested_value = !SuggestedValue().IsEmpty();
if (placeholder_text.IsEmpty()) {
if (placeholder)
UserAgentShadowRoot()->RemoveChild(placeholder);
return;
}
if (!placeholder) {
auto* new_element = MakeGarbageCollected<HTMLDivElement>(GetDocument());
placeholder = new_element;
placeholder->SetShadowPseudoId(AtomicString("-webkit-input-placeholder"));
placeholder->setAttribute(html_names::kIdAttr,
shadow_element_names::kIdPlaceholder);
placeholder->SetInlineStyleProperty(
CSSPropertyID::kDisplay,
IsPlaceholderVisible() ? CSSValueID::kBlock : CSSValueID::kNone, true);
UserAgentShadowRoot()->InsertBefore(placeholder, InnerEditorElement());
}
if (is_suggested_value) {
placeholder->SetInlineStyleProperty(CSSPropertyID::kUserSelect,
CSSValueID::kNone, true);
} else {
placeholder->RemoveInlineStyleProperty(CSSPropertyID::kUserSelect);
}
String normalized_value = placeholder_text;
// https://html.spec.whatwg.org/multipage/form-elements.html#attr-textarea-placeholder
ReplaceCRWithNewLine(normalized_value);
placeholder->setTextContent(normalized_value);
}
String HTMLTextAreaElement::GetPlaceholderValue() const {
return !SuggestedValue().IsEmpty()
? SuggestedValue()
: FastGetAttribute(html_names::kPlaceholderAttr);
}
bool HTMLTextAreaElement::IsInteractiveContent() const {
return true;
}
void HTMLTextAreaElement::CloneNonAttributePropertiesFrom(
const Element& source,
CloneChildrenFlag flag) {
const auto& source_element = To<HTMLTextAreaElement>(source);
SetValueCommon(source_element.value(),
TextFieldEventBehavior::kDispatchNoEvent,
TextControlSetValueSelection::kSetSelectionToEnd);
is_dirty_ = source_element.is_dirty_;
TextControlElement::CloneNonAttributePropertiesFrom(source, flag);
}
} // namespace blink