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 "nsIFormControl.h"
30 #include "nsLayoutUtils.h"
31 #include "nsLinebreakConverter.h"
32 #include "nsPresContext.h"
33 #include "nsReadableUtils.h"
34 #include "nsStyleConsts.h"
35 #include "nsTextControlFrame.h"
36 #include "nsThreadUtils.h"
37 #include "nsXULControllers.h"
39 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(TextArea
)
41 namespace mozilla::dom
{
43 HTMLTextAreaElement::HTMLTextAreaElement(
44 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
,
45 FromParser aFromParser
)
46 : TextControlElement(std::move(aNodeInfo
), aFromParser
,
47 FormControlType::Textarea
),
48 mDoneAddingChildren(!aFromParser
),
49 mInhibitStateRestoration(!!(aFromParser
& FROM_PARSER_FRAGMENT
)),
50 mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown
),
51 mState(TextControlState::Construct(this)) {
52 AddMutationObserver(this);
54 // Set up our default state. By default we're enabled (since we're
55 // a control type that can be disabled but not actually disabled right now),
56 // optional, read-write, and valid. Also by default we don't have to show
57 // validity UI and so forth.
58 AddStatesSilently(ElementState::ENABLED
| ElementState::OPTIONAL_
|
59 ElementState::READWRITE
| ElementState::VALID
|
60 ElementState::VALUE_EMPTY
);
61 RemoveStatesSilently(ElementState::READONLY
);
64 HTMLTextAreaElement::~HTMLTextAreaElement() {
69 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTextAreaElement
)
71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTextAreaElement
,
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity
)
74 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers
)
76 tmp
->mState
->Traverse(cb
);
78 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
80 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTextAreaElement
,
82 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity
)
83 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers
)
85 tmp
->mState
->Unlink();
87 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
89 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLTextAreaElement
,
92 nsIConstraintValidation
)
94 // nsIDOMHTMLTextAreaElement
96 nsresult
HTMLTextAreaElement::Clone(dom::NodeInfo
* aNodeInfo
,
97 nsINode
** aResult
) const {
99 RefPtr
<HTMLTextAreaElement
> it
= new (aNodeInfo
->NodeInfoManager())
100 HTMLTextAreaElement(do_AddRef(aNodeInfo
));
102 nsresult rv
= const_cast<HTMLTextAreaElement
*>(this)->CopyInnerTo(it
);
103 NS_ENSURE_SUCCESS(rv
, rv
);
105 it
->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive
);
112 void HTMLTextAreaElement::Select() {
113 if (FocusState() != FocusTristate::eUnfocusable
) {
114 if (RefPtr
<nsFocusManager
> fm
= nsFocusManager::GetFocusManager()) {
115 fm
->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL
);
119 SetSelectionRange(0, UINT32_MAX
, Optional
<nsAString
>(), IgnoreErrors());
122 void HTMLTextAreaElement::SelectAll() {
123 // FIXME(emilio): Should we try to call Select(), which will avoid flushing?
124 if (nsTextControlFrame
* tf
=
125 do_QueryFrame(GetPrimaryFrame(FlushType::Frames
))) {
130 bool HTMLTextAreaElement::IsHTMLFocusable(IsFocusableFlags aFlags
,
132 int32_t* aTabIndex
) {
133 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
134 aFlags
, aIsFocusable
, aTabIndex
)) {
138 // disabled textareas are not focusable
139 *aIsFocusable
= !IsDisabled();
143 int32_t HTMLTextAreaElement::TabIndexDefault() { return 0; }
145 void HTMLTextAreaElement::GetType(nsAString
& aType
) {
146 aType
.AssignLiteral("textarea");
149 void HTMLTextAreaElement::GetValue(nsAString
& aValue
) {
150 GetValueInternal(aValue
, true);
151 MOZ_ASSERT(aValue
.FindChar(static_cast<char16_t
>('\r')) == -1);
154 void HTMLTextAreaElement::GetValueInternal(nsAString
& aValue
,
155 bool aIgnoreWrap
) const {
157 mState
->GetValue(aValue
, aIgnoreWrap
, /* aForDisplay = */ true);
160 nsIEditor
* HTMLTextAreaElement::GetEditorForBindings() {
161 if (!GetPrimaryFrame()) {
162 GetPrimaryFrame(FlushType::Frames
);
164 return GetTextEditor();
167 TextEditor
* HTMLTextAreaElement::GetTextEditor() {
169 return mState
->GetTextEditor();
172 TextEditor
* HTMLTextAreaElement::GetTextEditorWithoutCreation() const {
174 return mState
->GetTextEditorWithoutCreation();
177 nsISelectionController
* HTMLTextAreaElement::GetSelectionController() {
179 return mState
->GetSelectionController();
182 nsFrameSelection
* HTMLTextAreaElement::GetConstFrameSelection() {
184 return mState
->GetConstFrameSelection();
187 nsresult
HTMLTextAreaElement::BindToFrame(nsTextControlFrame
* aFrame
) {
188 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
190 return mState
->BindToFrame(aFrame
);
193 void HTMLTextAreaElement::UnbindFromFrame(nsTextControlFrame
* aFrame
) {
196 mState
->UnbindFromFrame(aFrame
);
200 nsresult
HTMLTextAreaElement::CreateEditor() {
202 return mState
->PrepareEditor();
205 void HTMLTextAreaElement::SetPreviewValue(const nsAString
& aValue
) {
207 mState
->SetPreviewText(aValue
, true);
210 void HTMLTextAreaElement::GetPreviewValue(nsAString
& aValue
) {
212 mState
->GetPreviewText(aValue
);
215 void HTMLTextAreaElement::EnablePreview() {
216 if (mIsPreviewEnabled
) {
220 mIsPreviewEnabled
= true;
221 // Reconstruct the frame to append an anonymous preview node
222 nsLayoutUtils::PostRestyleEvent(this, RestyleHint
{0},
223 nsChangeHint_ReconstructFrame
);
226 bool HTMLTextAreaElement::IsPreviewEnabled() { return mIsPreviewEnabled
; }
228 nsresult
HTMLTextAreaElement::SetValueInternal(
229 const nsAString
& aValue
, const ValueSetterOptions
& aOptions
) {
232 // Need to set the value changed flag here if our value has in fact changed
233 // (i.e. if ValueSetterOption::SetValueChanged is in aOptions), so that
234 // retrieves the correct value if needed.
235 if (aOptions
.contains(ValueSetterOption::SetValueChanged
)) {
236 SetValueChanged(true);
239 if (!mState
->SetValue(aValue
, aOptions
)) {
240 return NS_ERROR_OUT_OF_MEMORY
;
246 void HTMLTextAreaElement::SetValue(const nsAString
& aValue
,
247 ErrorResult
& aError
) {
248 // If the value has been set by a script, we basically want to keep the
249 // current change event state. If the element is ready to fire a change
250 // event, we should keep it that way. Otherwise, we should make sure the
251 // element will not fire any event because of the script interaction.
253 // NOTE: this is currently quite expensive work (too much string
254 // manipulation). We should probably optimize that.
255 nsAutoString currentValue
;
256 GetValueInternal(currentValue
, true);
258 nsresult rv
= SetValueInternal(
260 {ValueSetterOption::ByContentAPI
, ValueSetterOption::SetValueChanged
,
261 ValueSetterOption::MoveCursorToEndIfValueChanged
});
262 if (NS_WARN_IF(NS_FAILED(rv
))) {
267 if (mFocusedValue
.Equals(currentValue
)) {
268 GetValueInternal(mFocusedValue
, true);
272 void HTMLTextAreaElement::SetUserInput(const nsAString
& aValue
,
273 nsIPrincipal
& aSubjectPrincipal
) {
274 SetValueInternal(aValue
, {ValueSetterOption::BySetUserInputAPI
,
275 ValueSetterOption::SetValueChanged
,
276 ValueSetterOption::MoveCursorToEndIfValueChanged
});
279 void HTMLTextAreaElement::SetValueChanged(bool aValueChanged
) {
282 bool previousValue
= mValueChanged
;
283 mValueChanged
= aValueChanged
;
284 if (!aValueChanged
&& !mState
->IsEmpty()) {
285 mState
->EmptyValue();
287 if (mValueChanged
== previousValue
) {
290 UpdateTooLongValidityState();
291 UpdateTooShortValidityState();
292 UpdateValidityElementStates(true);
295 void HTMLTextAreaElement::SetLastValueChangeWasInteractive(
296 bool aWasInteractive
) {
297 if (aWasInteractive
== mLastValueChangeWasInteractive
) {
300 mLastValueChangeWasInteractive
= aWasInteractive
;
301 const bool wasValid
= IsValid();
302 UpdateTooLongValidityState();
303 UpdateTooShortValidityState();
304 if (wasValid
!= IsValid()) {
305 UpdateValidityElementStates(true);
309 void HTMLTextAreaElement::GetDefaultValue(nsAString
& aDefaultValue
,
310 ErrorResult
& aError
) const {
311 if (!nsContentUtils::GetNodeTextContent(this, false, aDefaultValue
,
313 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
317 void HTMLTextAreaElement::SetDefaultValue(const nsAString
& aDefaultValue
,
318 ErrorResult
& aError
) {
319 // setting the value of an textarea element using `.defaultValue = "foo"`
320 // must be interpreted as a two-step operation:
321 // 1. clearing all child nodes
322 // 2. adding a new text node with the new content
323 // Step 1 must therefore collapse the Selection to 0.
324 // Calling `SetNodeTextContent()` with an empty string will do that for us.
325 nsContentUtils::SetNodeTextContent(this, EmptyString(), true);
326 nsresult rv
= nsContentUtils::SetNodeTextContent(this, aDefaultValue
, true);
327 if (NS_SUCCEEDED(rv
) && !mValueChanged
) {
335 bool HTMLTextAreaElement::ParseAttribute(int32_t aNamespaceID
,
337 const nsAString
& aValue
,
338 nsIPrincipal
* aMaybeScriptedPrincipal
,
339 nsAttrValue
& aResult
) {
340 if (aNamespaceID
== kNameSpaceID_None
) {
341 if (aAttribute
== nsGkAtoms::maxlength
||
342 aAttribute
== nsGkAtoms::minlength
) {
343 return aResult
.ParseNonNegativeIntValue(aValue
);
344 } else if (aAttribute
== nsGkAtoms::cols
) {
345 aResult
.ParseIntWithFallback(aValue
, DEFAULT_COLS
);
347 } else if (aAttribute
== nsGkAtoms::rows
) {
348 aResult
.ParseIntWithFallback(aValue
, DEFAULT_ROWS_TEXTAREA
);
350 } else if (aAttribute
== nsGkAtoms::autocomplete
) {
351 aResult
.ParseAtomArray(aValue
);
355 return TextControlElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
356 aMaybeScriptedPrincipal
, aResult
);
359 void HTMLTextAreaElement::MapAttributesIntoRule(
360 MappedDeclarationsBuilder
& aBuilder
) {
362 const nsAttrValue
* value
= aBuilder
.GetAttr(nsGkAtoms::wrap
);
363 if (value
&& value
->Type() == nsAttrValue::eString
&&
364 value
->Equals(nsGkAtoms::OFF
, eIgnoreCase
)) {
365 // Equivalent to expanding `white-space; pre`
366 aBuilder
.SetKeywordValue(eCSSProperty_white_space_collapse
,
367 StyleWhiteSpaceCollapse::Preserve
);
368 aBuilder
.SetKeywordValue(eCSSProperty_text_wrap_mode
,
369 StyleTextWrapMode::Nowrap
);
372 nsGenericHTMLFormControlElementWithState::MapDivAlignAttributeInto(aBuilder
);
373 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder
);
376 nsChangeHint
HTMLTextAreaElement::GetAttributeChangeHint(
377 const nsAtom
* aAttribute
, int32_t aModType
) const {
378 nsChangeHint retval
=
379 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
380 aAttribute
, aModType
);
382 const bool isAdditionOrRemoval
=
383 aModType
== MutationEvent_Binding::ADDITION
||
384 aModType
== MutationEvent_Binding::REMOVAL
;
386 if (aAttribute
== nsGkAtoms::rows
|| aAttribute
== nsGkAtoms::cols
) {
387 retval
|= NS_STYLE_HINT_REFLOW
;
388 } else if (aAttribute
== nsGkAtoms::wrap
) {
389 retval
|= nsChangeHint_ReconstructFrame
;
390 } else if (aAttribute
== nsGkAtoms::placeholder
&& isAdditionOrRemoval
) {
391 retval
|= nsChangeHint_ReconstructFrame
;
397 HTMLTextAreaElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
398 static const MappedAttributeEntry attributes
[] = {{nsGkAtoms::wrap
},
401 static const MappedAttributeEntry
* const map
[] = {
403 sDivAlignAttributeMap
,
407 return FindAttributeDependence(aAttribute
, map
);
410 nsMapRuleToAttributesFunc
HTMLTextAreaElement::GetAttributeMappingFunction()
412 return &MapAttributesIntoRule
;
415 bool HTMLTextAreaElement::IsDisabledForEvents(WidgetEvent
* aEvent
) {
416 return IsElementDisabledForEvents(aEvent
, GetPrimaryFrame());
419 void HTMLTextAreaElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
420 aVisitor
.mCanHandle
= false;
421 if (IsDisabledForEvents(aVisitor
.mEvent
)) {
425 // Don't dispatch a second select event if we are already handling
427 if (aVisitor
.mEvent
->mMessage
== eFormSelect
) {
428 if (mHandlingSelect
) {
431 mHandlingSelect
= true;
434 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
435 // Set mWantsPreHandleEvent and fire change event in PreHandleEvent to
436 // prevent it breaks event target chain creation.
437 aVisitor
.mWantsPreHandleEvent
= true;
440 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor
);
443 nsresult
HTMLTextAreaElement::PreHandleEvent(EventChainVisitor
& aVisitor
) {
444 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
445 // Fire onchange (if necessary), before we do the blur, bug 370521.
446 FireChangeEventIfNeeded();
448 return nsGenericHTMLFormControlElementWithState::PreHandleEvent(aVisitor
);
451 void HTMLTextAreaElement::FireChangeEventIfNeeded() {
453 GetValueInternal(value
, true);
455 // NOTE(emilio): This is not quite on the spec, but matches <input>, see
456 // https://github.com/whatwg/html/issues/10011 and
457 // https://github.com/whatwg/html/issues/10013
459 SetUserInteracted(true);
462 if (mFocusedValue
.Equals(value
)) {
466 // Dispatch the change event.
467 mFocusedValue
= value
;
468 nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u
"change"_ns
,
469 CanBubble::eYes
, Cancelable::eNo
);
472 nsresult
HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
) {
473 if (aVisitor
.mEvent
->mMessage
== eFormSelect
) {
474 mHandlingSelect
= false;
476 if (aVisitor
.mEvent
->mMessage
== eFocus
) {
477 GetValueInternal(mFocusedValue
, true);
482 void HTMLTextAreaElement::DoneAddingChildren(bool aHaveNotified
) {
483 if (!mValueChanged
) {
484 if (!mDoneAddingChildren
) {
485 // Reset now that we're done adding children if the content sink tried to
486 // sneak some text in without calling AppendChildTo.
490 if (!mInhibitStateRestoration
) {
492 RestoreFormControlState();
496 mDoneAddingChildren
= true;
499 // Controllers Methods
501 nsIControllers
* HTMLTextAreaElement::GetControllers(ErrorResult
& aError
) {
503 mControllers
= new nsXULControllers();
505 aError
.Throw(NS_ERROR_FAILURE
);
509 RefPtr
<nsBaseCommandController
> commandController
=
510 nsBaseCommandController::CreateEditorController();
511 if (!commandController
) {
512 aError
.Throw(NS_ERROR_FAILURE
);
516 mControllers
->AppendController(commandController
);
518 commandController
= nsBaseCommandController::CreateEditingController();
519 if (!commandController
) {
520 aError
.Throw(NS_ERROR_FAILURE
);
524 mControllers
->AppendController(commandController
);
530 nsresult
HTMLTextAreaElement::GetControllers(nsIControllers
** aResult
) {
531 NS_ENSURE_ARG_POINTER(aResult
);
534 *aResult
= GetControllers(error
);
535 NS_IF_ADDREF(*aResult
);
537 return error
.StealNSResult();
540 uint32_t HTMLTextAreaElement::GetTextLength() {
546 Nullable
<uint32_t> HTMLTextAreaElement::GetSelectionStart(ErrorResult
& aError
) {
547 uint32_t selStart
, selEnd
;
548 GetSelectionRange(&selStart
, &selEnd
, aError
);
549 return Nullable
<uint32_t>(selStart
);
552 void HTMLTextAreaElement::SetSelectionStart(
553 const Nullable
<uint32_t>& aSelectionStart
, ErrorResult
& aError
) {
555 mState
->SetSelectionStart(aSelectionStart
, aError
);
558 Nullable
<uint32_t> HTMLTextAreaElement::GetSelectionEnd(ErrorResult
& aError
) {
559 uint32_t selStart
, selEnd
;
560 GetSelectionRange(&selStart
, &selEnd
, aError
);
561 return Nullable
<uint32_t>(selEnd
);
564 void HTMLTextAreaElement::SetSelectionEnd(
565 const Nullable
<uint32_t>& aSelectionEnd
, ErrorResult
& aError
) {
567 mState
->SetSelectionEnd(aSelectionEnd
, aError
);
570 void HTMLTextAreaElement::GetSelectionRange(uint32_t* aSelectionStart
,
571 uint32_t* aSelectionEnd
,
574 return mState
->GetSelectionRange(aSelectionStart
, aSelectionEnd
, aRv
);
577 void HTMLTextAreaElement::GetSelectionDirection(nsAString
& aDirection
,
578 ErrorResult
& aError
) {
580 mState
->GetSelectionDirectionString(aDirection
, aError
);
583 void HTMLTextAreaElement::SetSelectionDirection(const nsAString
& aDirection
,
584 ErrorResult
& aError
) {
586 mState
->SetSelectionDirection(aDirection
, aError
);
589 void HTMLTextAreaElement::SetSelectionRange(
590 uint32_t aSelectionStart
, uint32_t aSelectionEnd
,
591 const Optional
<nsAString
>& aDirection
, ErrorResult
& aError
) {
593 mState
->SetSelectionRange(aSelectionStart
, aSelectionEnd
, aDirection
, aError
);
596 void HTMLTextAreaElement::SetRangeText(const nsAString
& aReplacement
,
599 mState
->SetRangeText(aReplacement
, aRv
);
602 void HTMLTextAreaElement::SetRangeText(const nsAString
& aReplacement
,
603 uint32_t aStart
, uint32_t aEnd
,
604 SelectionMode aSelectMode
,
607 mState
->SetRangeText(aReplacement
, aStart
, aEnd
, aSelectMode
, aRv
);
610 void HTMLTextAreaElement::GetValueFromSetRangeText(nsAString
& aValue
) {
611 GetValueInternal(aValue
, false);
614 nsresult
HTMLTextAreaElement::SetValueFromSetRangeText(
615 const nsAString
& aValue
) {
616 return SetValueInternal(aValue
, {ValueSetterOption::ByContentAPI
,
617 ValueSetterOption::BySetRangeTextAPI
,
618 ValueSetterOption::SetValueChanged
});
621 nsresult
HTMLTextAreaElement::Reset() {
622 nsAutoString resetVal
;
623 GetDefaultValue(resetVal
, IgnoreErrors());
624 SetValueChanged(false);
625 SetUserInteracted(false);
627 nsresult rv
= SetValueInternal(resetVal
, ValueSetterOption::ByInternalAPI
);
628 NS_ENSURE_SUCCESS(rv
, rv
);
634 HTMLTextAreaElement::SubmitNamesValues(FormData
* aFormData
) {
636 // Get the name (if no name, no submit)
639 GetAttr(nsGkAtoms::name
, name
);
640 if (name
.IsEmpty()) {
648 GetValueInternal(value
, false);
653 const nsresult rv
= aFormData
->AddNameValuePair(name
, value
);
658 // Submit dirname=dir
659 return SubmitDirnameDir(aFormData
);
662 void HTMLTextAreaElement::SaveState() {
663 // Only save if value != defaultValue (bug 62713)
664 PresState
* state
= nullptr;
666 state
= GetPrimaryPresState();
669 GetValueInternal(value
, true);
671 if (NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
672 value
, nsLinebreakConverter::eLinebreakPlatform
,
673 nsLinebreakConverter::eLinebreakContent
))) {
674 NS_ERROR("Converting linebreaks failed!");
678 state
->contentData() =
679 TextContentData(value
, mLastValueChangeWasInteractive
);
683 if (mDisabledChanged
) {
685 state
= GetPrimaryPresState();
688 // We do not want to save the real disabled state but the disabled
690 state
->disabled() = HasAttr(nsGkAtoms::disabled
);
691 state
->disabledSet() = true;
696 bool HTMLTextAreaElement::RestoreState(PresState
* aState
) {
697 const PresContentData
& state
= aState
->contentData();
699 if (state
.type() == PresContentData::TTextContentData
) {
701 SetValue(state
.get_TextContentData().value(), rv
);
702 ENSURE_SUCCESS(rv
, false);
703 if (state
.get_TextContentData().lastValueChangeWasInteractive()) {
704 SetLastValueChangeWasInteractive(true);
707 if (aState
->disabledSet() && !aState
->disabled()) {
708 SetDisabled(false, IgnoreErrors());
714 void HTMLTextAreaElement::UpdateValidityElementStates(bool aNotify
) {
715 AutoStateChangeNotifier
notifier(*this, aNotify
);
716 RemoveStatesSilently(ElementState::VALIDITY_STATES
);
717 if (!IsCandidateForConstraintValidation()) {
722 state
|= ElementState::VALID
;
723 if (mUserInteracted
) {
724 state
|= ElementState::USER_VALID
;
727 state
|= ElementState::INVALID
;
728 if (mUserInteracted
) {
729 state
|= ElementState::USER_INVALID
;
732 AddStatesSilently(state
);
735 nsresult
HTMLTextAreaElement::BindToTree(BindContext
& aContext
,
738 nsGenericHTMLFormControlElementWithState::BindToTree(aContext
, aParent
);
739 NS_ENSURE_SUCCESS(rv
, rv
);
741 // Set direction based on value if dir=auto
742 ResetDirFormAssociatedElement(this, false, HasDirAuto());
744 // If there is a disabled fieldset in the parent chain, the element is now
745 // barred from constraint validation and can't suffer from value missing.
746 UpdateValueMissingValidityState();
747 UpdateBarredFromConstraintValidation();
749 // And now make sure our state is up to date
750 UpdateValidityElementStates(false);
755 void HTMLTextAreaElement::UnbindFromTree(UnbindContext
& aContext
) {
756 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext
);
758 // We might be no longer disabled because of parent chain changed.
759 UpdateValueMissingValidityState();
760 UpdateBarredFromConstraintValidation();
762 // And now make sure our state is up to date
763 UpdateValidityElementStates(false);
766 void HTMLTextAreaElement::BeforeSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
767 const nsAttrValue
* aValue
,
769 if (aNotify
&& aName
== nsGkAtoms::disabled
&&
770 aNameSpaceID
== kNameSpaceID_None
) {
771 mDisabledChanged
= true;
774 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
775 aNameSpaceID
, aName
, aValue
, aNotify
);
778 void HTMLTextAreaElement::CharacterDataChanged(nsIContent
* aContent
,
779 const CharacterDataChangeInfo
&) {
780 ContentChanged(aContent
);
783 void HTMLTextAreaElement::ContentAppended(nsIContent
* aFirstNewContent
) {
784 ContentChanged(aFirstNewContent
);
787 void HTMLTextAreaElement::ContentInserted(nsIContent
* aChild
) {
788 ContentChanged(aChild
);
791 void HTMLTextAreaElement::ContentRemoved(nsIContent
* aChild
,
792 nsIContent
* aPreviousSibling
) {
793 ContentChanged(aChild
);
796 void HTMLTextAreaElement::ContentChanged(nsIContent
* aContent
) {
797 if (!mValueChanged
&& mDoneAddingChildren
&&
798 nsContentUtils::IsInSameAnonymousTree(this, aContent
)) {
799 if (mState
->IsSelectionCached()) {
800 // In case the content is *replaced*, i.e. by calling
801 // `.textContent = "foo";`,
802 // firstly the old content is removed, then the new content is added.
803 // As per wpt, this must collapse the selection to 0.
804 // Removing and adding of an element is routed through here, but due to
805 // the script runner `Reset()` is only invoked after the append operation.
806 // Therefore, `Reset()` would adjust the Selection to the new value, not
808 // By forcing a selection update here, the selection is reset in order to
809 // comply with the wpt.
810 auto& props
= mState
->GetSelectionProperties();
811 nsAutoString resetVal
;
812 GetDefaultValue(resetVal
, IgnoreErrors());
813 props
.SetMaxLength(resetVal
.Length());
814 props
.SetStart(props
.GetStart());
815 props
.SetEnd(props
.GetEnd());
817 // We should wait all ranges finish handling the mutation before updating
818 // the anonymous subtree with a call of Reset.
819 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
820 "ResetHTMLTextAreaElementIfValueHasNotChangedYet",
821 [self
= RefPtr
{this}]() {
822 // However, if somebody has already changed the value, we don't need
823 // to keep doing this.
824 if (!self
->mValueChanged
) {
831 void HTMLTextAreaElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
832 const nsAttrValue
* aValue
,
833 const nsAttrValue
* aOldValue
,
834 nsIPrincipal
* aSubjectPrincipal
,
836 if (aNameSpaceID
== kNameSpaceID_None
) {
837 if (aName
== nsGkAtoms::required
|| aName
== nsGkAtoms::disabled
||
838 aName
== nsGkAtoms::readonly
) {
839 if (aName
== nsGkAtoms::disabled
) {
840 // This *has* to be called *before* validity state check because
841 // UpdateBarredFromConstraintValidation and
842 // UpdateValueMissingValidityState depend on our disabled state.
843 UpdateDisabledState(aNotify
);
846 if (aName
== nsGkAtoms::required
) {
847 // This *has* to be called *before* UpdateValueMissingValidityState
848 // because UpdateValueMissingValidityState depends on our required
850 UpdateRequiredState(!!aValue
, aNotify
);
853 if (aName
== nsGkAtoms::readonly
&& !!aValue
!= !!aOldValue
) {
854 UpdateReadOnlyState(aNotify
);
857 UpdateValueMissingValidityState();
859 // This *has* to be called *after* validity has changed.
860 if (aName
== nsGkAtoms::readonly
|| aName
== nsGkAtoms::disabled
) {
861 UpdateBarredFromConstraintValidation();
863 UpdateValidityElementStates(aNotify
);
864 } else if (aName
== nsGkAtoms::autocomplete
) {
865 // Clear the cached @autocomplete attribute state.
866 mAutocompleteAttrState
= nsContentUtils::eAutocompleteAttrState_Unknown
;
867 } else if (aName
== nsGkAtoms::maxlength
) {
868 UpdateTooLongValidityState();
869 UpdateValidityElementStates(aNotify
);
870 } else if (aName
== nsGkAtoms::minlength
) {
871 UpdateTooShortValidityState();
872 UpdateValidityElementStates(aNotify
);
873 } else if (aName
== nsGkAtoms::placeholder
) {
874 if (nsTextControlFrame
* f
= do_QueryFrame(GetPrimaryFrame())) {
875 f
->PlaceholderChanged(aOldValue
, aValue
);
877 UpdatePlaceholderShownState();
878 } else if (aName
== nsGkAtoms::dir
&& aValue
&&
879 aValue
->Equals(nsGkAtoms::_auto
, eIgnoreCase
)) {
880 ResetDirFormAssociatedElement(this, aNotify
, true);
884 return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
885 aNameSpaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
888 nsresult
HTMLTextAreaElement::CopyInnerTo(Element
* aDest
) {
889 nsresult rv
= nsGenericHTMLFormControlElementWithState::CopyInnerTo(aDest
);
890 NS_ENSURE_SUCCESS(rv
, rv
);
892 if (mValueChanged
|| aDest
->OwnerDoc()->IsStaticDocument()) {
893 // Set our value on the clone.
894 auto* dest
= static_cast<HTMLTextAreaElement
*>(aDest
);
897 GetValueInternal(value
, true);
899 // SetValueInternal handles setting mValueChanged for us. dest is a fresh
900 // element so setting its value can't really run script.
902 NS_FAILED(rv
= MOZ_KnownLive(dest
)->SetValueInternal(
903 value
, {ValueSetterOption::SetValueChanged
})))) {
911 bool HTMLTextAreaElement::IsMutable() const { return !IsDisabledOrReadOnly(); }
913 void HTMLTextAreaElement::SetCustomValidity(const nsAString
& aError
) {
914 ConstraintValidation::SetCustomValidity(aError
);
915 UpdateValidityElementStates(true);
918 bool HTMLTextAreaElement::IsTooLong() {
919 if (!mValueChanged
|| !mLastValueChangeWasInteractive
||
920 !HasAttr(nsGkAtoms::maxlength
)) {
924 int32_t maxLength
= MaxLength();
926 // Maxlength of -1 means parsing error.
927 if (maxLength
== -1) {
931 int32_t textLength
= GetTextLength();
933 return textLength
> maxLength
;
936 bool HTMLTextAreaElement::IsTooShort() {
937 if (!mValueChanged
|| !mLastValueChangeWasInteractive
||
938 !HasAttr(nsGkAtoms::minlength
)) {
942 int32_t minLength
= MinLength();
944 // Minlength of -1 means parsing error.
945 if (minLength
== -1) {
949 int32_t textLength
= GetTextLength();
951 return textLength
&& textLength
< minLength
;
954 bool HTMLTextAreaElement::IsValueMissing() const {
955 if (!Required() || !IsMutable()) {
958 return IsValueEmpty();
961 void HTMLTextAreaElement::UpdateTooLongValidityState() {
962 SetValidityState(VALIDITY_STATE_TOO_LONG
, IsTooLong());
965 void HTMLTextAreaElement::UpdateTooShortValidityState() {
966 SetValidityState(VALIDITY_STATE_TOO_SHORT
, IsTooShort());
969 void HTMLTextAreaElement::UpdateValueMissingValidityState() {
970 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, IsValueMissing());
973 void HTMLTextAreaElement::UpdateBarredFromConstraintValidation() {
974 SetBarredFromConstraintValidation(
975 HasAttr(nsGkAtoms::readonly
) ||
976 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR
) || IsDisabled());
979 nsresult
HTMLTextAreaElement::GetValidationMessage(
980 nsAString
& aValidationMessage
, ValidityStateType aType
) {
984 case VALIDITY_STATE_TOO_LONG
: {
985 nsAutoString message
;
986 int32_t maxLength
= MaxLength();
987 int32_t textLength
= GetTextLength();
988 nsAutoString strMaxLength
;
989 nsAutoString strTextLength
;
991 strMaxLength
.AppendInt(maxLength
);
992 strTextLength
.AppendInt(textLength
);
994 rv
= nsContentUtils::FormatMaybeLocalizedString(
995 message
, nsContentUtils::eDOM_PROPERTIES
, "FormValidationTextTooLong",
996 OwnerDoc(), strMaxLength
, strTextLength
);
997 aValidationMessage
= message
;
999 case VALIDITY_STATE_TOO_SHORT
: {
1000 nsAutoString message
;
1001 int32_t minLength
= MinLength();
1002 int32_t textLength
= GetTextLength();
1003 nsAutoString strMinLength
;
1004 nsAutoString strTextLength
;
1006 strMinLength
.AppendInt(minLength
);
1007 strTextLength
.AppendInt(textLength
);
1009 rv
= nsContentUtils::FormatMaybeLocalizedString(
1010 message
, nsContentUtils::eDOM_PROPERTIES
,
1011 "FormValidationTextTooShort", OwnerDoc(), strMinLength
,
1013 aValidationMessage
= message
;
1015 case VALIDITY_STATE_VALUE_MISSING
: {
1016 nsAutoString message
;
1017 rv
= nsContentUtils::GetMaybeLocalizedString(
1018 nsContentUtils::eDOM_PROPERTIES
, "FormValidationValueMissing",
1019 OwnerDoc(), message
);
1020 aValidationMessage
= message
;
1024 ConstraintValidation::GetValidationMessage(aValidationMessage
, aType
);
1030 bool HTMLTextAreaElement::IsSingleLineTextControl() const { return false; }
1032 bool HTMLTextAreaElement::IsTextArea() const { return true; }
1034 bool HTMLTextAreaElement::IsPasswordTextControl() const { return false; }
1036 Maybe
<int32_t> HTMLTextAreaElement::GetCols() {
1037 const nsAttrValue
* value
= GetParsedAttr(nsGkAtoms::cols
);
1038 if (!value
|| value
->Type() != nsAttrValue::eInteger
) {
1041 return Some(value
->GetIntegerValue());
1044 int32_t HTMLTextAreaElement::GetWrapCols() {
1045 nsHTMLTextWrap wrapProp
;
1046 TextControlElement::GetWrapPropertyEnum(this, wrapProp
);
1047 if (wrapProp
== TextControlElement::eHTMLTextWrap_Off
) {
1048 // do not wrap when wrap=off
1052 // Otherwise we just wrap at the given number of columns
1053 return GetColsOrDefault();
1056 int32_t HTMLTextAreaElement::GetRows() {
1057 const nsAttrValue
* attr
= GetParsedAttr(nsGkAtoms::rows
);
1058 if (attr
&& attr
->Type() == nsAttrValue::eInteger
) {
1059 int32_t rows
= attr
->GetIntegerValue();
1060 return (rows
<= 0) ? DEFAULT_ROWS_TEXTAREA
: rows
;
1063 return DEFAULT_ROWS_TEXTAREA
;
1066 void HTMLTextAreaElement::GetDefaultValueFromContent(nsAString
& aValue
, bool) {
1067 GetDefaultValue(aValue
, IgnoreErrors());
1070 bool HTMLTextAreaElement::ValueChanged() const { return mValueChanged
; }
1072 void HTMLTextAreaElement::GetTextEditorValue(nsAString
& aValue
) const {
1074 mState
->GetValue(aValue
, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
1077 void HTMLTextAreaElement::InitializeKeyboardEventListeners() {
1079 mState
->InitializeKeyboardEventListeners();
1082 void HTMLTextAreaElement::UpdatePlaceholderShownState() {
1083 SetStates(ElementState::PLACEHOLDER_SHOWN
,
1084 IsValueEmpty() && HasAttr(nsGkAtoms::placeholder
));
1087 void HTMLTextAreaElement::OnValueChanged(ValueChangeKind aKind
,
1088 bool aNewValueEmpty
,
1089 const nsAString
* aKnownNewValue
) {
1090 if (aKind
!= ValueChangeKind::Internal
) {
1091 mLastValueChangeWasInteractive
= aKind
== ValueChangeKind::UserInteraction
;
1094 if (aNewValueEmpty
!= IsValueEmpty()) {
1095 SetStates(ElementState::VALUE_EMPTY
, aNewValueEmpty
);
1096 UpdatePlaceholderShownState();
1099 // Update the validity state
1100 const bool validBefore
= IsValid();
1101 UpdateTooLongValidityState();
1102 UpdateTooShortValidityState();
1103 UpdateValueMissingValidityState();
1105 ResetDirFormAssociatedElement(this, true, HasDirAuto(), aKnownNewValue
);
1107 if (validBefore
!= IsValid()) {
1108 UpdateValidityElementStates(true);
1112 bool HTMLTextAreaElement::HasCachedSelection() {
1114 return mState
->IsSelectionCached();
1117 void HTMLTextAreaElement::SetUserInteracted(bool aInteracted
) {
1118 if (mUserInteracted
== aInteracted
) {
1121 mUserInteracted
= aInteracted
;
1122 UpdateValidityElementStates(true);
1125 void HTMLTextAreaElement::FieldSetDisabledChanged(bool aNotify
) {
1126 // This *has* to be called before UpdateBarredFromConstraintValidation and
1127 // UpdateValueMissingValidityState because these two functions depend on our
1129 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify
);
1131 UpdateValueMissingValidityState();
1132 UpdateBarredFromConstraintValidation();
1133 UpdateValidityElementStates(true);
1136 JSObject
* HTMLTextAreaElement::WrapNode(JSContext
* aCx
,
1137 JS::Handle
<JSObject
*> aGivenProto
) {
1138 return HTMLTextAreaElement_Binding::Wrap(aCx
, this, aGivenProto
);
1141 void HTMLTextAreaElement::GetAutocomplete(DOMString
& aValue
) {
1142 const nsAttrValue
* attributeVal
= GetParsedAttr(nsGkAtoms::autocomplete
);
1144 mAutocompleteAttrState
= nsContentUtils::SerializeAutocompleteAttribute(
1145 attributeVal
, aValue
, mAutocompleteAttrState
);
1148 } // namespace mozilla::dom