1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/HTMLTextAreaElement.h"
9 #include "mozAutoDocUpdate.h"
10 #include "mozilla/AsyncEventDispatcher.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/dom/FormData.h"
13 #include "mozilla/dom/HTMLTextAreaElementBinding.h"
14 #include "mozilla/dom/MutationEventBinding.h"
15 #include "mozilla/EventDispatcher.h"
16 #include "mozilla/MappedDeclarationsBuilder.h"
17 #include "mozilla/MouseEvents.h"
18 #include "mozilla/PresState.h"
19 #include "mozilla/TextControlState.h"
20 #include "nsAttrValueInlines.h"
21 #include "nsBaseCommandController.h"
22 #include "nsContentCreatorFunctions.h"
24 #include "nsFocusManager.h"
25 #include "nsIConstraintValidation.h"
26 #include "nsIControllers.h"
27 #include "mozilla/dom/Document.h"
28 #include "nsIFormControlFrame.h"
29 #include "nsIFormControl.h"
31 #include "nsITextControlFrame.h"
32 #include "nsLayoutUtils.h"
33 #include "nsLinebreakConverter.h"
34 #include "nsPresContext.h"
35 #include "nsReadableUtils.h"
36 #include "nsStyleConsts.h"
37 #include "nsTextControlFrame.h"
38 #include "nsThreadUtils.h"
39 #include "nsXULControllers.h"
41 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(TextArea
)
43 namespace mozilla::dom
{
45 HTMLTextAreaElement::HTMLTextAreaElement(
46 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
47 FromParser aFromParser
)
48 : TextControlElement(std::move(aNodeInfo
), aFromParser
,
49 FormControlType::Textarea
),
50 mDoneAddingChildren(!aFromParser
),
51 mInhibitStateRestoration(!!(aFromParser
& FROM_PARSER_FRAGMENT
)),
52 mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown
),
53 mState(TextControlState::Construct(this)) {
54 AddMutationObserver(this);
56 // Set up our default state. By default we're enabled (since we're
57 // a control type that can be disabled but not actually disabled right now),
58 // optional, read-write, and valid. Also by default we don't have to show
59 // validity UI and so forth.
60 AddStatesSilently(ElementState::ENABLED
| ElementState::OPTIONAL_
|
61 ElementState::READWRITE
| ElementState::VALID
|
62 ElementState::VALUE_EMPTY
);
63 RemoveStatesSilently(ElementState::READONLY
);
66 HTMLTextAreaElement::~HTMLTextAreaElement() {
71 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTextAreaElement
)
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTextAreaElement
,
75 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity
)
76 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers
)
78 tmp
->mState
->Traverse(cb
);
80 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
82 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTextAreaElement
,
84 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity
)
85 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers
)
87 tmp
->mState
->Unlink();
89 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
91 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLTextAreaElement
,
94 nsIConstraintValidation
)
96 // nsIDOMHTMLTextAreaElement
98 nsresult
HTMLTextAreaElement::Clone(dom::NodeInfo
* aNodeInfo
,
99 nsINode
** aResult
) const {
101 RefPtr
<HTMLTextAreaElement
> it
= new (aNodeInfo
->NodeInfoManager())
102 HTMLTextAreaElement(do_AddRef(aNodeInfo
));
104 nsresult rv
= const_cast<HTMLTextAreaElement
*>(this)->CopyInnerTo(it
);
105 NS_ENSURE_SUCCESS(rv
, rv
);
107 it
->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive
);
114 void HTMLTextAreaElement::Select() {
115 if (FocusState() != FocusTristate::eUnfocusable
) {
116 if (RefPtr
<nsFocusManager
> fm
= nsFocusManager::GetFocusManager()) {
117 fm
->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL
);
121 SetSelectionRange(0, UINT32_MAX
, mozilla::dom::Optional
<nsAString
>(),
126 HTMLTextAreaElement::SelectAll(nsPresContext
* aPresContext
) {
127 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(true);
129 if (formControlFrame
) {
130 formControlFrame
->SetFormProperty(nsGkAtoms::select
, u
""_ns
);
136 bool HTMLTextAreaElement::IsHTMLFocusable(bool aWithMouse
, bool* aIsFocusable
,
137 int32_t* aTabIndex
) {
138 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
139 aWithMouse
, aIsFocusable
, aTabIndex
)) {
143 // disabled textareas are not focusable
144 *aIsFocusable
= !IsDisabled();
148 int32_t HTMLTextAreaElement::TabIndexDefault() { return 0; }
150 void HTMLTextAreaElement::GetType(nsAString
& aType
) {
151 aType
.AssignLiteral("textarea");
154 void HTMLTextAreaElement::GetValue(nsAString
& aValue
) {
155 GetValueInternal(aValue
, true);
156 MOZ_ASSERT(aValue
.FindChar(static_cast<char16_t
>('\r')) == -1);
159 void HTMLTextAreaElement::GetValueInternal(nsAString
& aValue
,
160 bool aIgnoreWrap
) const {
162 mState
->GetValue(aValue
, aIgnoreWrap
, /* aForDisplay = */ true);
165 nsIEditor
* HTMLTextAreaElement::GetEditorForBindings() {
166 if (!GetPrimaryFrame()) {
167 GetPrimaryFrame(FlushType::Frames
);
169 return GetTextEditor();
172 TextEditor
* HTMLTextAreaElement::GetTextEditor() {
174 return mState
->GetTextEditor();
177 TextEditor
* HTMLTextAreaElement::GetTextEditorWithoutCreation() const {
179 return mState
->GetTextEditorWithoutCreation();
182 nsISelectionController
* HTMLTextAreaElement::GetSelectionController() {
184 return mState
->GetSelectionController();
187 nsFrameSelection
* HTMLTextAreaElement::GetConstFrameSelection() {
189 return mState
->GetConstFrameSelection();
192 nsresult
HTMLTextAreaElement::BindToFrame(nsTextControlFrame
* aFrame
) {
193 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
195 return mState
->BindToFrame(aFrame
);
198 void HTMLTextAreaElement::UnbindFromFrame(nsTextControlFrame
* aFrame
) {
201 mState
->UnbindFromFrame(aFrame
);
205 nsresult
HTMLTextAreaElement::CreateEditor() {
207 return mState
->PrepareEditor();
210 void HTMLTextAreaElement::SetPreviewValue(const nsAString
& aValue
) {
212 mState
->SetPreviewText(aValue
, true);
215 void HTMLTextAreaElement::GetPreviewValue(nsAString
& aValue
) {
217 mState
->GetPreviewText(aValue
);
220 void HTMLTextAreaElement::EnablePreview() {
221 if (mIsPreviewEnabled
) {
225 mIsPreviewEnabled
= true;
226 // Reconstruct the frame to append an anonymous preview node
227 nsLayoutUtils::PostRestyleEvent(this, RestyleHint
{0},
228 nsChangeHint_ReconstructFrame
);
231 bool HTMLTextAreaElement::IsPreviewEnabled() { return mIsPreviewEnabled
; }
233 nsresult
HTMLTextAreaElement::SetValueInternal(
234 const nsAString
& aValue
, const ValueSetterOptions
& aOptions
) {
237 // Need to set the value changed flag here if our value has in fact changed
238 // (i.e. if ValueSetterOption::SetValueChanged is in aOptions), so that
239 // retrieves the correct value if needed.
240 if (aOptions
.contains(ValueSetterOption::SetValueChanged
)) {
241 SetValueChanged(true);
244 if (!mState
->SetValue(aValue
, aOptions
)) {
245 return NS_ERROR_OUT_OF_MEMORY
;
251 void HTMLTextAreaElement::SetValue(const nsAString
& aValue
,
252 ErrorResult
& aError
) {
253 // If the value has been set by a script, we basically want to keep the
254 // current change event state. If the element is ready to fire a change
255 // event, we should keep it that way. Otherwise, we should make sure the
256 // element will not fire any event because of the script interaction.
258 // NOTE: this is currently quite expensive work (too much string
259 // manipulation). We should probably optimize that.
260 nsAutoString currentValue
;
261 GetValueInternal(currentValue
, true);
263 nsresult rv
= SetValueInternal(
265 {ValueSetterOption::ByContentAPI
, ValueSetterOption::SetValueChanged
,
266 ValueSetterOption::MoveCursorToEndIfValueChanged
});
267 if (NS_WARN_IF(NS_FAILED(rv
))) {
272 if (mFocusedValue
.Equals(currentValue
)) {
273 GetValueInternal(mFocusedValue
, true);
277 void HTMLTextAreaElement::SetUserInput(const nsAString
& aValue
,
278 nsIPrincipal
& aSubjectPrincipal
) {
279 SetValueInternal(aValue
, {ValueSetterOption::BySetUserInputAPI
,
280 ValueSetterOption::SetValueChanged
,
281 ValueSetterOption::MoveCursorToEndIfValueChanged
});
284 void HTMLTextAreaElement::SetValueChanged(bool aValueChanged
) {
287 bool previousValue
= mValueChanged
;
288 mValueChanged
= aValueChanged
;
289 if (!aValueChanged
&& !mState
->IsEmpty()) {
290 mState
->EmptyValue();
292 if (mValueChanged
== previousValue
) {
295 UpdateTooLongValidityState();
296 UpdateTooShortValidityState();
297 UpdateValidityElementStates(true);
300 void HTMLTextAreaElement::SetLastValueChangeWasInteractive(
301 bool aWasInteractive
) {
302 if (aWasInteractive
== mLastValueChangeWasInteractive
) {
305 mLastValueChangeWasInteractive
= aWasInteractive
;
306 const bool wasValid
= IsValid();
307 UpdateTooLongValidityState();
308 UpdateTooShortValidityState();
309 if (wasValid
!= IsValid()) {
310 UpdateValidityElementStates(true);
314 void HTMLTextAreaElement::GetDefaultValue(nsAString
& aDefaultValue
,
315 ErrorResult
& aError
) const {
316 if (!nsContentUtils::GetNodeTextContent(this, false, aDefaultValue
,
318 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
322 void HTMLTextAreaElement::SetDefaultValue(const nsAString
& aDefaultValue
,
323 ErrorResult
& aError
) {
324 // setting the value of an textarea element using `.defaultValue = "foo"`
325 // must be interpreted as a two-step operation:
326 // 1. clearing all child nodes
327 // 2. adding a new text node with the new content
328 // Step 1 must therefore collapse the Selection to 0.
329 // Calling `SetNodeTextContent()` with an empty string will do that for us.
330 nsContentUtils::SetNodeTextContent(this, EmptyString(), true);
331 nsresult rv
= nsContentUtils::SetNodeTextContent(this, aDefaultValue
, true);
332 if (NS_SUCCEEDED(rv
) && !mValueChanged
) {
340 bool HTMLTextAreaElement::ParseAttribute(int32_t aNamespaceID
,
342 const nsAString
& aValue
,
343 nsIPrincipal
* aMaybeScriptedPrincipal
,
344 nsAttrValue
& aResult
) {
345 if (aNamespaceID
== kNameSpaceID_None
) {
346 if (aAttribute
== nsGkAtoms::maxlength
||
347 aAttribute
== nsGkAtoms::minlength
) {
348 return aResult
.ParseNonNegativeIntValue(aValue
);
349 } else if (aAttribute
== nsGkAtoms::cols
) {
350 aResult
.ParseIntWithFallback(aValue
, DEFAULT_COLS
);
352 } else if (aAttribute
== nsGkAtoms::rows
) {
353 aResult
.ParseIntWithFallback(aValue
, DEFAULT_ROWS_TEXTAREA
);
355 } else if (aAttribute
== nsGkAtoms::autocomplete
) {
356 aResult
.ParseAtomArray(aValue
);
360 return TextControlElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
361 aMaybeScriptedPrincipal
, aResult
);
364 void HTMLTextAreaElement::MapAttributesIntoRule(
365 MappedDeclarationsBuilder
& aBuilder
) {
367 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::wrap
);
368 if (value
&& value
->Type() == nsAttrValue::eString
&&
369 value
->Equals(nsGkAtoms::OFF
, eIgnoreCase
)) {
370 // Equivalent to expanding `white-space; pre`
371 aBuilder
.SetKeywordValue(eCSSProperty_white_space_collapse
,
372 StyleWhiteSpaceCollapse::Preserve
);
373 aBuilder
.SetKeywordValue(eCSSProperty_text_wrap_mode
,
374 StyleTextWrapMode::Nowrap
);
377 nsGenericHTMLFormControlElementWithState::MapDivAlignAttributeInto(aBuilder
);
378 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder
);
381 nsChangeHint
HTMLTextAreaElement::GetAttributeChangeHint(
382 const nsAtom
* aAttribute
, int32_t aModType
) const {
383 nsChangeHint retval
=
384 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
385 aAttribute
, aModType
);
387 const bool isAdditionOrRemoval
=
388 aModType
== MutationEvent_Binding::ADDITION
||
389 aModType
== MutationEvent_Binding::REMOVAL
;
391 if (aAttribute
== nsGkAtoms::rows
|| aAttribute
== nsGkAtoms::cols
) {
392 retval
|= NS_STYLE_HINT_REFLOW
;
393 } else if (aAttribute
== nsGkAtoms::wrap
) {
394 retval
|= nsChangeHint_ReconstructFrame
;
395 } else if (aAttribute
== nsGkAtoms::placeholder
&& isAdditionOrRemoval
) {
396 retval
|= nsChangeHint_ReconstructFrame
;
402 HTMLTextAreaElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
403 static const MappedAttributeEntry attributes
[] = {{nsGkAtoms::wrap
},
406 static const MappedAttributeEntry
* const map
[] = {
408 sDivAlignAttributeMap
,
412 return FindAttributeDependence(aAttribute
, map
);
415 nsMapRuleToAttributesFunc
HTMLTextAreaElement::GetAttributeMappingFunction()
417 return &MapAttributesIntoRule
;
420 bool HTMLTextAreaElement::IsDisabledForEvents(WidgetEvent
* aEvent
) {
421 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(false);
422 nsIFrame
* formFrame
= do_QueryFrame(formControlFrame
);
423 return IsElementDisabledForEvents(aEvent
, formFrame
);
426 void HTMLTextAreaElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
427 aVisitor
.mCanHandle
= false;
428 if (IsDisabledForEvents(aVisitor
.mEvent
)) {
432 // Don't dispatch a second select event if we are already handling
434 if (aVisitor
.mEvent
->mMessage
== eFormSelect
) {
435 if (mHandlingSelect
) {
438 mHandlingSelect
= true;
441 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
442 // Set mWantsPreHandleEvent and fire change event in PreHandleEvent to
443 // prevent it breaks event target chain creation.
444 aVisitor
.mWantsPreHandleEvent
= true;
447 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor
);
450 nsresult
HTMLTextAreaElement::PreHandleEvent(EventChainVisitor
& aVisitor
) {
451 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
452 // Fire onchange (if necessary), before we do the blur, bug 370521.
453 FireChangeEventIfNeeded();
455 return nsGenericHTMLFormControlElementWithState::PreHandleEvent(aVisitor
);
458 void HTMLTextAreaElement::FireChangeEventIfNeeded() {
460 GetValueInternal(value
, true);
462 // NOTE(emilio): This is not quite on the spec, but matches <input>, see
463 // https://github.com/whatwg/html/issues/10011 and
464 // https://github.com/whatwg/html/issues/10013
466 SetUserInteracted(true);
469 if (mFocusedValue
.Equals(value
)) {
473 // Dispatch the change event.
474 mFocusedValue
= value
;
475 nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u
"change"_ns
,
476 CanBubble::eYes
, Cancelable::eNo
);
479 nsresult
HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
) {
480 if (aVisitor
.mEvent
->mMessage
== eFormSelect
) {
481 mHandlingSelect
= false;
483 if (aVisitor
.mEvent
->mMessage
== eFocus
) {
484 GetValueInternal(mFocusedValue
, true);
489 void HTMLTextAreaElement::DoneAddingChildren(bool aHaveNotified
) {
490 if (!mValueChanged
) {
491 if (!mDoneAddingChildren
) {
492 // Reset now that we're done adding children if the content sink tried to
493 // sneak some text in without calling AppendChildTo.
497 if (!mInhibitStateRestoration
) {
499 RestoreFormControlState();
503 mDoneAddingChildren
= true;
506 // Controllers Methods
508 nsIControllers
* HTMLTextAreaElement::GetControllers(ErrorResult
& aError
) {
510 mControllers
= new nsXULControllers();
512 aError
.Throw(NS_ERROR_FAILURE
);
516 RefPtr
<nsBaseCommandController
> commandController
=
517 nsBaseCommandController::CreateEditorController();
518 if (!commandController
) {
519 aError
.Throw(NS_ERROR_FAILURE
);
523 mControllers
->AppendController(commandController
);
525 commandController
= nsBaseCommandController::CreateEditingController();
526 if (!commandController
) {
527 aError
.Throw(NS_ERROR_FAILURE
);
531 mControllers
->AppendController(commandController
);
537 nsresult
HTMLTextAreaElement::GetControllers(nsIControllers
** aResult
) {
538 NS_ENSURE_ARG_POINTER(aResult
);
541 *aResult
= GetControllers(error
);
542 NS_IF_ADDREF(*aResult
);
544 return error
.StealNSResult();
547 uint32_t HTMLTextAreaElement::GetTextLength() {
553 Nullable
<uint32_t> HTMLTextAreaElement::GetSelectionStart(ErrorResult
& aError
) {
554 uint32_t selStart
, selEnd
;
555 GetSelectionRange(&selStart
, &selEnd
, aError
);
556 return Nullable
<uint32_t>(selStart
);
559 void HTMLTextAreaElement::SetSelectionStart(
560 const Nullable
<uint32_t>& aSelectionStart
, ErrorResult
& aError
) {
562 mState
->SetSelectionStart(aSelectionStart
, aError
);
565 Nullable
<uint32_t> HTMLTextAreaElement::GetSelectionEnd(ErrorResult
& aError
) {
566 uint32_t selStart
, selEnd
;
567 GetSelectionRange(&selStart
, &selEnd
, aError
);
568 return Nullable
<uint32_t>(selEnd
);
571 void HTMLTextAreaElement::SetSelectionEnd(
572 const Nullable
<uint32_t>& aSelectionEnd
, ErrorResult
& aError
) {
574 mState
->SetSelectionEnd(aSelectionEnd
, aError
);
577 void HTMLTextAreaElement::GetSelectionRange(uint32_t* aSelectionStart
,
578 uint32_t* aSelectionEnd
,
581 return mState
->GetSelectionRange(aSelectionStart
, aSelectionEnd
, aRv
);
584 void HTMLTextAreaElement::GetSelectionDirection(nsAString
& aDirection
,
585 ErrorResult
& aError
) {
587 mState
->GetSelectionDirectionString(aDirection
, aError
);
590 void HTMLTextAreaElement::SetSelectionDirection(const nsAString
& aDirection
,
591 ErrorResult
& aError
) {
593 mState
->SetSelectionDirection(aDirection
, aError
);
596 void HTMLTextAreaElement::SetSelectionRange(
597 uint32_t aSelectionStart
, uint32_t aSelectionEnd
,
598 const Optional
<nsAString
>& aDirection
, ErrorResult
& aError
) {
600 mState
->SetSelectionRange(aSelectionStart
, aSelectionEnd
, aDirection
, aError
);
603 void HTMLTextAreaElement::SetRangeText(const nsAString
& aReplacement
,
606 mState
->SetRangeText(aReplacement
, aRv
);
609 void HTMLTextAreaElement::SetRangeText(const nsAString
& aReplacement
,
610 uint32_t aStart
, uint32_t aEnd
,
611 SelectionMode aSelectMode
,
614 mState
->SetRangeText(aReplacement
, aStart
, aEnd
, aSelectMode
, aRv
);
617 void HTMLTextAreaElement::GetValueFromSetRangeText(nsAString
& aValue
) {
618 GetValueInternal(aValue
, false);
621 nsresult
HTMLTextAreaElement::SetValueFromSetRangeText(
622 const nsAString
& aValue
) {
623 return SetValueInternal(aValue
, {ValueSetterOption::ByContentAPI
,
624 ValueSetterOption::BySetRangeTextAPI
,
625 ValueSetterOption::SetValueChanged
});
628 void HTMLTextAreaElement::SetDirectionFromValue(bool aNotify
,
629 const nsAString
* aKnownValue
) {
633 aKnownValue
= &value
;
635 SetDirectionalityFromValue(this, *aKnownValue
, aNotify
);
638 nsresult
HTMLTextAreaElement::Reset() {
639 nsAutoString resetVal
;
640 GetDefaultValue(resetVal
, IgnoreErrors());
641 SetValueChanged(false);
642 SetUserInteracted(false);
644 nsresult rv
= SetValueInternal(resetVal
, ValueSetterOption::ByInternalAPI
);
645 NS_ENSURE_SUCCESS(rv
, rv
);
651 HTMLTextAreaElement::SubmitNamesValues(FormData
* aFormData
) {
653 // Get the name (if no name, no submit)
656 GetAttr(nsGkAtoms::name
, name
);
657 if (name
.IsEmpty()) {
665 GetValueInternal(value
, false);
670 const nsresult rv
= aFormData
->AddNameValuePair(name
, value
);
675 // Submit dirname=dir
676 return SubmitDirnameDir(aFormData
);
679 void HTMLTextAreaElement::SaveState() {
680 // Only save if value != defaultValue (bug 62713)
681 PresState
* state
= nullptr;
683 state
= GetPrimaryPresState();
686 GetValueInternal(value
, true);
688 if (NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
689 value
, nsLinebreakConverter::eLinebreakPlatform
,
690 nsLinebreakConverter::eLinebreakContent
))) {
691 NS_ERROR("Converting linebreaks failed!");
695 state
->contentData() =
696 TextContentData(value
, mLastValueChangeWasInteractive
);
700 if (mDisabledChanged
) {
702 state
= GetPrimaryPresState();
705 // We do not want to save the real disabled state but the disabled
707 state
->disabled() = HasAttr(nsGkAtoms::disabled
);
708 state
->disabledSet() = true;
713 bool HTMLTextAreaElement::RestoreState(PresState
* aState
) {
714 const PresContentData
& state
= aState
->contentData();
716 if (state
.type() == PresContentData::TTextContentData
) {
718 SetValue(state
.get_TextContentData().value(), rv
);
719 ENSURE_SUCCESS(rv
, false);
720 if (state
.get_TextContentData().lastValueChangeWasInteractive()) {
721 SetLastValueChangeWasInteractive(true);
724 if (aState
->disabledSet() && !aState
->disabled()) {
725 SetDisabled(false, IgnoreErrors());
731 void HTMLTextAreaElement::UpdateValidityElementStates(bool aNotify
) {
732 AutoStateChangeNotifier
notifier(*this, aNotify
);
733 RemoveStatesSilently(ElementState::VALIDITY_STATES
);
734 if (!IsCandidateForConstraintValidation()) {
739 state
|= ElementState::VALID
;
740 if (mUserInteracted
) {
741 state
|= ElementState::USER_VALID
;
744 state
|= ElementState::INVALID
;
745 if (mUserInteracted
) {
746 state
|= ElementState::USER_INVALID
;
749 AddStatesSilently(state
);
752 nsresult
HTMLTextAreaElement::BindToTree(BindContext
& aContext
,
755 nsGenericHTMLFormControlElementWithState::BindToTree(aContext
, aParent
);
756 NS_ENSURE_SUCCESS(rv
, rv
);
758 // Set direction based on value if dir=auto
760 SetDirectionFromValue(false);
763 // If there is a disabled fieldset in the parent chain, the element is now
764 // barred from constraint validation and can't suffer from value missing.
765 UpdateValueMissingValidityState();
766 UpdateBarredFromConstraintValidation();
768 // And now make sure our state is up to date
769 UpdateValidityElementStates(false);
774 void HTMLTextAreaElement::UnbindFromTree(UnbindContext
& aContext
) {
775 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext
);
777 // We might be no longer disabled because of parent chain changed.
778 UpdateValueMissingValidityState();
779 UpdateBarredFromConstraintValidation();
781 // And now make sure our state is up to date
782 UpdateValidityElementStates(false);
785 void HTMLTextAreaElement::BeforeSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
786 const nsAttrValue
* aValue
,
788 if (aNotify
&& aName
== nsGkAtoms::disabled
&&
789 aNameSpaceID
== kNameSpaceID_None
) {
790 mDisabledChanged
= true;
793 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
794 aNameSpaceID
, aName
, aValue
, aNotify
);
797 void HTMLTextAreaElement::CharacterDataChanged(nsIContent
* aContent
,
798 const CharacterDataChangeInfo
&) {
799 ContentChanged(aContent
);
802 void HTMLTextAreaElement::ContentAppended(nsIContent
* aFirstNewContent
) {
803 ContentChanged(aFirstNewContent
);
806 void HTMLTextAreaElement::ContentInserted(nsIContent
* aChild
) {
807 ContentChanged(aChild
);
810 void HTMLTextAreaElement::ContentRemoved(nsIContent
* aChild
,
811 nsIContent
* aPreviousSibling
) {
812 ContentChanged(aChild
);
815 void HTMLTextAreaElement::ContentChanged(nsIContent
* aContent
) {
816 if (!mValueChanged
&& mDoneAddingChildren
&&
817 nsContentUtils::IsInSameAnonymousTree(this, aContent
)) {
818 if (mState
->IsSelectionCached()) {
819 // In case the content is *replaced*, i.e. by calling
820 // `.textContent = "foo";`,
821 // firstly the old content is removed, then the new content is added.
822 // As per wpt, this must collapse the selection to 0.
823 // Removing and adding of an element is routed through here, but due to
824 // the script runner `Reset()` is only invoked after the append operation.
825 // Therefore, `Reset()` would adjust the Selection to the new value, not
827 // By forcing a selection update here, the selection is reset in order to
828 // comply with the wpt.
829 auto& props
= mState
->GetSelectionProperties();
830 nsAutoString resetVal
;
831 GetDefaultValue(resetVal
, IgnoreErrors());
832 props
.SetMaxLength(resetVal
.Length());
833 props
.SetStart(props
.GetStart());
834 props
.SetEnd(props
.GetEnd());
836 // We should wait all ranges finish handling the mutation before updating
837 // the anonymous subtree with a call of Reset.
838 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
839 "ResetHTMLTextAreaElementIfValueHasNotChangedYet",
840 [self
= RefPtr
{this}]() {
841 // However, if somebody has already changed the value, we don't need
842 // to keep doing this.
843 if (!self
->mValueChanged
) {
850 void HTMLTextAreaElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
851 const nsAttrValue
* aValue
,
852 const nsAttrValue
* aOldValue
,
853 nsIPrincipal
* aSubjectPrincipal
,
855 if (aNameSpaceID
== kNameSpaceID_None
) {
856 if (aName
== nsGkAtoms::required
|| aName
== nsGkAtoms::disabled
||
857 aName
== nsGkAtoms::readonly
) {
858 if (aName
== nsGkAtoms::disabled
) {
859 // This *has* to be called *before* validity state check because
860 // UpdateBarredFromConstraintValidation and
861 // UpdateValueMissingValidityState depend on our disabled state.
862 UpdateDisabledState(aNotify
);
865 if (aName
== nsGkAtoms::required
) {
866 // This *has* to be called *before* UpdateValueMissingValidityState
867 // because UpdateValueMissingValidityState depends on our required
869 UpdateRequiredState(!!aValue
, aNotify
);
872 if (aName
== nsGkAtoms::readonly
&& !!aValue
!= !!aOldValue
) {
873 UpdateReadOnlyState(aNotify
);
876 UpdateValueMissingValidityState();
878 // This *has* to be called *after* validity has changed.
879 if (aName
== nsGkAtoms::readonly
|| aName
== nsGkAtoms::disabled
) {
880 UpdateBarredFromConstraintValidation();
882 UpdateValidityElementStates(aNotify
);
883 } else if (aName
== nsGkAtoms::autocomplete
) {
884 // Clear the cached @autocomplete attribute state.
885 mAutocompleteAttrState
= nsContentUtils::eAutocompleteAttrState_Unknown
;
886 } else if (aName
== nsGkAtoms::maxlength
) {
887 UpdateTooLongValidityState();
888 UpdateValidityElementStates(aNotify
);
889 } else if (aName
== nsGkAtoms::minlength
) {
890 UpdateTooShortValidityState();
891 UpdateValidityElementStates(aNotify
);
892 } else if (aName
== nsGkAtoms::placeholder
) {
893 if (nsTextControlFrame
* f
= do_QueryFrame(GetPrimaryFrame())) {
894 f
->PlaceholderChanged(aOldValue
, aValue
);
896 UpdatePlaceholderShownState();
897 } else if (aName
== nsGkAtoms::dir
&& aValue
&&
898 aValue
->Equals(nsGkAtoms::_auto
, eIgnoreCase
)) {
899 SetDirectionFromValue(aNotify
);
903 return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
904 aNameSpaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
907 nsresult
HTMLTextAreaElement::CopyInnerTo(Element
* aDest
) {
908 nsresult rv
= nsGenericHTMLFormControlElementWithState::CopyInnerTo(aDest
);
909 NS_ENSURE_SUCCESS(rv
, rv
);
911 if (mValueChanged
|| aDest
->OwnerDoc()->IsStaticDocument()) {
912 // Set our value on the clone.
913 auto* dest
= static_cast<HTMLTextAreaElement
*>(aDest
);
916 GetValueInternal(value
, true);
918 // SetValueInternal handles setting mValueChanged for us. dest is a fresh
919 // element so setting its value can't really run script.
921 NS_FAILED(rv
= MOZ_KnownLive(dest
)->SetValueInternal(
922 value
, {ValueSetterOption::SetValueChanged
})))) {
930 bool HTMLTextAreaElement::IsMutable() const { return !IsDisabledOrReadOnly(); }
932 void HTMLTextAreaElement::SetCustomValidity(const nsAString
& aError
) {
933 ConstraintValidation::SetCustomValidity(aError
);
934 UpdateValidityElementStates(true);
937 bool HTMLTextAreaElement::IsTooLong() {
938 if (!mValueChanged
|| !mLastValueChangeWasInteractive
||
939 !HasAttr(nsGkAtoms::maxlength
)) {
943 int32_t maxLength
= MaxLength();
945 // Maxlength of -1 means parsing error.
946 if (maxLength
== -1) {
950 int32_t textLength
= GetTextLength();
952 return textLength
> maxLength
;
955 bool HTMLTextAreaElement::IsTooShort() {
956 if (!mValueChanged
|| !mLastValueChangeWasInteractive
||
957 !HasAttr(nsGkAtoms::minlength
)) {
961 int32_t minLength
= MinLength();
963 // Minlength of -1 means parsing error.
964 if (minLength
== -1) {
968 int32_t textLength
= GetTextLength();
970 return textLength
&& textLength
< minLength
;
973 bool HTMLTextAreaElement::IsValueMissing() const {
974 if (!Required() || !IsMutable()) {
977 return IsValueEmpty();
980 void HTMLTextAreaElement::UpdateTooLongValidityState() {
981 SetValidityState(VALIDITY_STATE_TOO_LONG
, IsTooLong());
984 void HTMLTextAreaElement::UpdateTooShortValidityState() {
985 SetValidityState(VALIDITY_STATE_TOO_SHORT
, IsTooShort());
988 void HTMLTextAreaElement::UpdateValueMissingValidityState() {
989 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, IsValueMissing());
992 void HTMLTextAreaElement::UpdateBarredFromConstraintValidation() {
993 SetBarredFromConstraintValidation(
994 HasAttr(nsGkAtoms::readonly
) ||
995 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR
) || IsDisabled());
998 nsresult
HTMLTextAreaElement::GetValidationMessage(
999 nsAString
& aValidationMessage
, ValidityStateType aType
) {
1000 nsresult rv
= NS_OK
;
1003 case VALIDITY_STATE_TOO_LONG
: {
1004 nsAutoString message
;
1005 int32_t maxLength
= MaxLength();
1006 int32_t textLength
= GetTextLength();
1007 nsAutoString strMaxLength
;
1008 nsAutoString strTextLength
;
1010 strMaxLength
.AppendInt(maxLength
);
1011 strTextLength
.AppendInt(textLength
);
1013 rv
= nsContentUtils::FormatMaybeLocalizedString(
1014 message
, nsContentUtils::eDOM_PROPERTIES
, "FormValidationTextTooLong",
1015 OwnerDoc(), strMaxLength
, strTextLength
);
1016 aValidationMessage
= message
;
1018 case VALIDITY_STATE_TOO_SHORT
: {
1019 nsAutoString message
;
1020 int32_t minLength
= MinLength();
1021 int32_t textLength
= GetTextLength();
1022 nsAutoString strMinLength
;
1023 nsAutoString strTextLength
;
1025 strMinLength
.AppendInt(minLength
);
1026 strTextLength
.AppendInt(textLength
);
1028 rv
= nsContentUtils::FormatMaybeLocalizedString(
1029 message
, nsContentUtils::eDOM_PROPERTIES
,
1030 "FormValidationTextTooShort", OwnerDoc(), strMinLength
,
1032 aValidationMessage
= message
;
1034 case VALIDITY_STATE_VALUE_MISSING
: {
1035 nsAutoString message
;
1036 rv
= nsContentUtils::GetMaybeLocalizedString(
1037 nsContentUtils::eDOM_PROPERTIES
, "FormValidationValueMissing",
1038 OwnerDoc(), message
);
1039 aValidationMessage
= message
;
1043 ConstraintValidation::GetValidationMessage(aValidationMessage
, aType
);
1049 bool HTMLTextAreaElement::IsSingleLineTextControl() const { return false; }
1051 bool HTMLTextAreaElement::IsTextArea() const { return true; }
1053 bool HTMLTextAreaElement::IsPasswordTextControl() const { return false; }
1055 int32_t HTMLTextAreaElement::GetCols() { return Cols(); }
1057 int32_t HTMLTextAreaElement::GetWrapCols() {
1058 nsHTMLTextWrap wrapProp
;
1059 TextControlElement::GetWrapPropertyEnum(this, wrapProp
);
1060 if (wrapProp
== TextControlElement::eHTMLTextWrap_Off
) {
1061 // do not wrap when wrap=off
1065 // Otherwise we just wrap at the given number of columns
1069 int32_t HTMLTextAreaElement::GetRows() {
1070 const nsAttrValue
* attr
= GetParsedAttr(nsGkAtoms::rows
);
1071 if (attr
&& attr
->Type() == nsAttrValue::eInteger
) {
1072 int32_t rows
= attr
->GetIntegerValue();
1073 return (rows
<= 0) ? DEFAULT_ROWS_TEXTAREA
: rows
;
1076 return DEFAULT_ROWS_TEXTAREA
;
1079 void HTMLTextAreaElement::GetDefaultValueFromContent(nsAString
& aValue
, bool) {
1080 GetDefaultValue(aValue
, IgnoreErrors());
1083 bool HTMLTextAreaElement::ValueChanged() const { return mValueChanged
; }
1085 void HTMLTextAreaElement::GetTextEditorValue(nsAString
& aValue
) const {
1087 mState
->GetValue(aValue
, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
1090 void HTMLTextAreaElement::InitializeKeyboardEventListeners() {
1092 mState
->InitializeKeyboardEventListeners();
1095 void HTMLTextAreaElement::UpdatePlaceholderShownState() {
1096 SetStates(ElementState::PLACEHOLDER_SHOWN
,
1097 IsValueEmpty() && HasAttr(nsGkAtoms::placeholder
));
1100 void HTMLTextAreaElement::OnValueChanged(ValueChangeKind aKind
,
1101 bool aNewValueEmpty
,
1102 const nsAString
* aKnownNewValue
) {
1103 if (aKind
!= ValueChangeKind::Internal
) {
1104 mLastValueChangeWasInteractive
= aKind
== ValueChangeKind::UserInteraction
;
1107 if (aNewValueEmpty
!= IsValueEmpty()) {
1108 SetStates(ElementState::VALUE_EMPTY
, aNewValueEmpty
);
1109 UpdatePlaceholderShownState();
1112 // Update the validity state
1113 const bool validBefore
= IsValid();
1114 UpdateTooLongValidityState();
1115 UpdateTooShortValidityState();
1116 UpdateValueMissingValidityState();
1119 SetDirectionFromValue(true, aKnownNewValue
);
1122 if (validBefore
!= IsValid()) {
1123 UpdateValidityElementStates(true);
1127 bool HTMLTextAreaElement::HasCachedSelection() {
1129 return mState
->IsSelectionCached();
1132 void HTMLTextAreaElement::SetUserInteracted(bool aInteracted
) {
1133 if (mUserInteracted
== aInteracted
) {
1136 mUserInteracted
= aInteracted
;
1137 UpdateValidityElementStates(true);
1140 void HTMLTextAreaElement::FieldSetDisabledChanged(bool aNotify
) {
1141 // This *has* to be called before UpdateBarredFromConstraintValidation and
1142 // UpdateValueMissingValidityState because these two functions depend on our
1144 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify
);
1146 UpdateValueMissingValidityState();
1147 UpdateBarredFromConstraintValidation();
1148 UpdateValidityElementStates(true);
1151 JSObject
* HTMLTextAreaElement::WrapNode(JSContext
* aCx
,
1152 JS::Handle
<JSObject
*> aGivenProto
) {
1153 return HTMLTextAreaElement_Binding::Wrap(aCx
, this, aGivenProto
);
1156 void HTMLTextAreaElement::GetAutocomplete(DOMString
& aValue
) {
1157 const nsAttrValue
* attributeVal
= GetParsedAttr(nsGkAtoms::autocomplete
);
1159 mAutocompleteAttrState
= nsContentUtils::SerializeAutocompleteAttribute(
1160 attributeVal
, aValue
, mAutocompleteAttrState
);
1163 } // namespace mozilla::dom