Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / html / HTMLTextAreaElement.cpp
blobbe4ba5a891f50aa745573658da0cd6fa33a27c35
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"
23 #include "nsError.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"
30 #include "nsIFrame.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() {
67 mState->Destroy();
68 mState = nullptr;
71 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTextAreaElement)
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTextAreaElement,
74 TextControlElement)
75 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
76 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
77 if (tmp->mState) {
78 tmp->mState->Traverse(cb);
80 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
82 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTextAreaElement,
83 TextControlElement)
84 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
85 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
86 if (tmp->mState) {
87 tmp->mState->Unlink();
89 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
91 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLTextAreaElement,
92 TextControlElement,
93 nsIMutationObserver,
94 nsIConstraintValidation)
96 // nsIDOMHTMLTextAreaElement
98 nsresult HTMLTextAreaElement::Clone(dom::NodeInfo* aNodeInfo,
99 nsINode** aResult) const {
100 *aResult = nullptr;
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);
108 it.forget(aResult);
109 return NS_OK;
112 // nsIContent
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>(),
122 IgnoreErrors());
125 NS_IMETHODIMP
126 HTMLTextAreaElement::SelectAll(nsPresContext* aPresContext) {
127 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
129 if (formControlFrame) {
130 formControlFrame->SetFormProperty(nsGkAtoms::select, u""_ns);
133 return NS_OK;
136 bool HTMLTextAreaElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
137 int32_t* aTabIndex) {
138 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
139 aWithMouse, aIsFocusable, aTabIndex)) {
140 return true;
143 // disabled textareas are not focusable
144 *aIsFocusable = !IsDisabled();
145 return false;
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 {
161 MOZ_ASSERT(mState);
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() {
173 MOZ_ASSERT(mState);
174 return mState->GetTextEditor();
177 TextEditor* HTMLTextAreaElement::GetTextEditorWithoutCreation() const {
178 MOZ_ASSERT(mState);
179 return mState->GetTextEditorWithoutCreation();
182 nsISelectionController* HTMLTextAreaElement::GetSelectionController() {
183 MOZ_ASSERT(mState);
184 return mState->GetSelectionController();
187 nsFrameSelection* HTMLTextAreaElement::GetConstFrameSelection() {
188 MOZ_ASSERT(mState);
189 return mState->GetConstFrameSelection();
192 nsresult HTMLTextAreaElement::BindToFrame(nsTextControlFrame* aFrame) {
193 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
194 MOZ_ASSERT(mState);
195 return mState->BindToFrame(aFrame);
198 void HTMLTextAreaElement::UnbindFromFrame(nsTextControlFrame* aFrame) {
199 MOZ_ASSERT(mState);
200 if (aFrame) {
201 mState->UnbindFromFrame(aFrame);
205 nsresult HTMLTextAreaElement::CreateEditor() {
206 MOZ_ASSERT(mState);
207 return mState->PrepareEditor();
210 void HTMLTextAreaElement::SetPreviewValue(const nsAString& aValue) {
211 MOZ_ASSERT(mState);
212 mState->SetPreviewText(aValue, true);
215 void HTMLTextAreaElement::GetPreviewValue(nsAString& aValue) {
216 MOZ_ASSERT(mState);
217 mState->GetPreviewText(aValue);
220 void HTMLTextAreaElement::EnablePreview() {
221 if (mIsPreviewEnabled) {
222 return;
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) {
235 MOZ_ASSERT(mState);
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;
248 return NS_OK;
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(
264 aValue,
265 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
266 ValueSetterOption::MoveCursorToEndIfValueChanged});
267 if (NS_WARN_IF(NS_FAILED(rv))) {
268 aError.Throw(rv);
269 return;
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) {
285 MOZ_ASSERT(mState);
287 bool previousValue = mValueChanged;
288 mValueChanged = aValueChanged;
289 if (!aValueChanged && !mState->IsEmpty()) {
290 mState->EmptyValue();
292 if (mValueChanged == previousValue) {
293 return;
295 UpdateTooLongValidityState();
296 UpdateTooShortValidityState();
297 UpdateValidityElementStates(true);
300 void HTMLTextAreaElement::SetLastValueChangeWasInteractive(
301 bool aWasInteractive) {
302 if (aWasInteractive == mLastValueChangeWasInteractive) {
303 return;
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,
317 fallible)) {
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) {
333 Reset();
335 if (NS_FAILED(rv)) {
336 aError.Throw(rv);
340 bool HTMLTextAreaElement::ParseAttribute(int32_t aNamespaceID,
341 nsAtom* aAttribute,
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);
351 return true;
352 } else if (aAttribute == nsGkAtoms::rows) {
353 aResult.ParseIntWithFallback(aValue, DEFAULT_ROWS_TEXTAREA);
354 return true;
355 } else if (aAttribute == nsGkAtoms::autocomplete) {
356 aResult.ParseAtomArray(aValue);
357 return true;
360 return TextControlElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
361 aMaybeScriptedPrincipal, aResult);
364 void HTMLTextAreaElement::MapAttributesIntoRule(
365 MappedDeclarationsBuilder& aBuilder) {
366 // wrap=off
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;
398 return retval;
401 NS_IMETHODIMP_(bool)
402 HTMLTextAreaElement::IsAttributeMapped(const nsAtom* aAttribute) const {
403 static const MappedAttributeEntry attributes[] = {{nsGkAtoms::wrap},
404 {nullptr}};
406 static const MappedAttributeEntry* const map[] = {
407 attributes,
408 sDivAlignAttributeMap,
409 sCommonAttributeMap,
412 return FindAttributeDependence(aAttribute, map);
415 nsMapRuleToAttributesFunc HTMLTextAreaElement::GetAttributeMappingFunction()
416 const {
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)) {
429 return;
432 // Don't dispatch a second select event if we are already handling
433 // one.
434 if (aVisitor.mEvent->mMessage == eFormSelect) {
435 if (mHandlingSelect) {
436 return;
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() {
459 nsString value;
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
465 if (mValueChanged) {
466 SetUserInteracted(true);
469 if (mFocusedValue.Equals(value)) {
470 return;
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);
486 return NS_OK;
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.
494 Reset();
497 if (!mInhibitStateRestoration) {
498 GenerateStateKey();
499 RestoreFormControlState();
503 mDoneAddingChildren = true;
506 // Controllers Methods
508 nsIControllers* HTMLTextAreaElement::GetControllers(ErrorResult& aError) {
509 if (!mControllers) {
510 mControllers = new nsXULControllers();
511 if (!mControllers) {
512 aError.Throw(NS_ERROR_FAILURE);
513 return nullptr;
516 RefPtr<nsBaseCommandController> commandController =
517 nsBaseCommandController::CreateEditorController();
518 if (!commandController) {
519 aError.Throw(NS_ERROR_FAILURE);
520 return nullptr;
523 mControllers->AppendController(commandController);
525 commandController = nsBaseCommandController::CreateEditingController();
526 if (!commandController) {
527 aError.Throw(NS_ERROR_FAILURE);
528 return nullptr;
531 mControllers->AppendController(commandController);
534 return mControllers;
537 nsresult HTMLTextAreaElement::GetControllers(nsIControllers** aResult) {
538 NS_ENSURE_ARG_POINTER(aResult);
540 ErrorResult error;
541 *aResult = GetControllers(error);
542 NS_IF_ADDREF(*aResult);
544 return error.StealNSResult();
547 uint32_t HTMLTextAreaElement::GetTextLength() {
548 nsAutoString val;
549 GetValue(val);
550 return val.Length();
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) {
561 MOZ_ASSERT(mState);
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) {
573 MOZ_ASSERT(mState);
574 mState->SetSelectionEnd(aSelectionEnd, aError);
577 void HTMLTextAreaElement::GetSelectionRange(uint32_t* aSelectionStart,
578 uint32_t* aSelectionEnd,
579 ErrorResult& aRv) {
580 MOZ_ASSERT(mState);
581 return mState->GetSelectionRange(aSelectionStart, aSelectionEnd, aRv);
584 void HTMLTextAreaElement::GetSelectionDirection(nsAString& aDirection,
585 ErrorResult& aError) {
586 MOZ_ASSERT(mState);
587 mState->GetSelectionDirectionString(aDirection, aError);
590 void HTMLTextAreaElement::SetSelectionDirection(const nsAString& aDirection,
591 ErrorResult& aError) {
592 MOZ_ASSERT(mState);
593 mState->SetSelectionDirection(aDirection, aError);
596 void HTMLTextAreaElement::SetSelectionRange(
597 uint32_t aSelectionStart, uint32_t aSelectionEnd,
598 const Optional<nsAString>& aDirection, ErrorResult& aError) {
599 MOZ_ASSERT(mState);
600 mState->SetSelectionRange(aSelectionStart, aSelectionEnd, aDirection, aError);
603 void HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement,
604 ErrorResult& aRv) {
605 MOZ_ASSERT(mState);
606 mState->SetRangeText(aReplacement, aRv);
609 void HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement,
610 uint32_t aStart, uint32_t aEnd,
611 SelectionMode aSelectMode,
612 ErrorResult& aRv) {
613 MOZ_ASSERT(mState);
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) {
630 nsAutoString value;
631 if (!aKnownValue) {
632 GetValue(value);
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);
647 return NS_OK;
650 NS_IMETHODIMP
651 HTMLTextAreaElement::SubmitNamesValues(FormData* aFormData) {
653 // Get the name (if no name, no submit)
655 nsAutoString name;
656 GetAttr(nsGkAtoms::name, name);
657 if (name.IsEmpty()) {
658 return NS_OK;
662 // Get the value
664 nsAutoString value;
665 GetValueInternal(value, false);
668 // Submit name=value
670 const nsresult rv = aFormData->AddNameValuePair(name, value);
671 if (NS_FAILED(rv)) {
672 return rv;
675 // Submit dirname=dir
676 return SubmitDirnameDir(aFormData);
679 void HTMLTextAreaElement::SaveState() {
680 // Only save if value != defaultValue (bug 62713)
681 PresState* state = nullptr;
682 if (mValueChanged) {
683 state = GetPrimaryPresState();
684 if (state) {
685 nsAutoString value;
686 GetValueInternal(value, true);
688 if (NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
689 value, nsLinebreakConverter::eLinebreakPlatform,
690 nsLinebreakConverter::eLinebreakContent))) {
691 NS_ERROR("Converting linebreaks failed!");
692 return;
695 state->contentData() =
696 TextContentData(value, mLastValueChangeWasInteractive);
700 if (mDisabledChanged) {
701 if (!state) {
702 state = GetPrimaryPresState();
704 if (state) {
705 // We do not want to save the real disabled state but the disabled
706 // attribute.
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) {
717 ErrorResult rv;
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());
728 return false;
731 void HTMLTextAreaElement::UpdateValidityElementStates(bool aNotify) {
732 AutoStateChangeNotifier notifier(*this, aNotify);
733 RemoveStatesSilently(ElementState::VALIDITY_STATES);
734 if (!IsCandidateForConstraintValidation()) {
735 return;
737 ElementState state;
738 if (IsValid()) {
739 state |= ElementState::VALID;
740 if (mUserInteracted) {
741 state |= ElementState::USER_VALID;
743 } else {
744 state |= ElementState::INVALID;
745 if (mUserInteracted) {
746 state |= ElementState::USER_INVALID;
749 AddStatesSilently(state);
752 nsresult HTMLTextAreaElement::BindToTree(BindContext& aContext,
753 nsINode& aParent) {
754 nsresult rv =
755 nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
756 NS_ENSURE_SUCCESS(rv, rv);
758 // Set direction based on value if dir=auto
759 if (HasDirAuto()) {
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);
771 return rv;
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,
787 bool aNotify) {
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
826 // to 0.
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) {
844 self->Reset();
846 }));
850 void HTMLTextAreaElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
851 const nsAttrValue* aValue,
852 const nsAttrValue* aOldValue,
853 nsIPrincipal* aSubjectPrincipal,
854 bool aNotify) {
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
868 // state.
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);
915 nsAutoString value;
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.
920 if (NS_WARN_IF(
921 NS_FAILED(rv = MOZ_KnownLive(dest)->SetValueInternal(
922 value, {ValueSetterOption::SetValueChanged})))) {
923 return rv;
927 return NS_OK;
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)) {
940 return false;
943 int32_t maxLength = MaxLength();
945 // Maxlength of -1 means parsing error.
946 if (maxLength == -1) {
947 return false;
950 int32_t textLength = GetTextLength();
952 return textLength > maxLength;
955 bool HTMLTextAreaElement::IsTooShort() {
956 if (!mValueChanged || !mLastValueChangeWasInteractive ||
957 !HasAttr(nsGkAtoms::minlength)) {
958 return false;
961 int32_t minLength = MinLength();
963 // Minlength of -1 means parsing error.
964 if (minLength == -1) {
965 return false;
968 int32_t textLength = GetTextLength();
970 return textLength && textLength < minLength;
973 bool HTMLTextAreaElement::IsValueMissing() const {
974 if (!Required() || !IsMutable()) {
975 return false;
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;
1002 switch (aType) {
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;
1017 } break;
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,
1031 strTextLength);
1032 aValidationMessage = message;
1033 } break;
1034 case VALIDITY_STATE_VALUE_MISSING: {
1035 nsAutoString message;
1036 rv = nsContentUtils::GetMaybeLocalizedString(
1037 nsContentUtils::eDOM_PROPERTIES, "FormValidationValueMissing",
1038 OwnerDoc(), message);
1039 aValidationMessage = message;
1040 } break;
1041 default:
1042 rv =
1043 ConstraintValidation::GetValidationMessage(aValidationMessage, aType);
1046 return rv;
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
1062 return 0;
1065 // Otherwise we just wrap at the given number of columns
1066 return GetCols();
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 {
1086 MOZ_ASSERT(mState);
1087 mState->GetValue(aValue, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
1090 void HTMLTextAreaElement::InitializeKeyboardEventListeners() {
1091 MOZ_ASSERT(mState);
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();
1118 if (HasDirAuto()) {
1119 SetDirectionFromValue(true, aKnownNewValue);
1122 if (validBefore != IsValid()) {
1123 UpdateValidityElementStates(true);
1127 bool HTMLTextAreaElement::HasCachedSelection() {
1128 MOZ_ASSERT(mState);
1129 return mState->IsSelectionCached();
1132 void HTMLTextAreaElement::SetUserInteracted(bool aInteracted) {
1133 if (mUserInteracted == aInteracted) {
1134 return;
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
1143 // disabled state.
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