Bug 1899500 - Implement explicit resource management in Baseline compiler. r=arai
[gecko.git] / dom / html / HTMLTextAreaElement.cpp
blob3cceef9ac0824da4a19e1d907edb758b02f699ec
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 "nsIFormControl.h"
29 #include "nsIFrame.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() {
65 mState->Destroy();
66 mState = nullptr;
69 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTextAreaElement)
71 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTextAreaElement,
72 TextControlElement)
73 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
74 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
75 if (tmp->mState) {
76 tmp->mState->Traverse(cb);
78 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
80 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTextAreaElement,
81 TextControlElement)
82 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
83 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
84 if (tmp->mState) {
85 tmp->mState->Unlink();
87 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
89 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLTextAreaElement,
90 TextControlElement,
91 nsIMutationObserver,
92 nsIConstraintValidation)
94 // nsIDOMHTMLTextAreaElement
96 nsresult HTMLTextAreaElement::Clone(dom::NodeInfo* aNodeInfo,
97 nsINode** aResult) const {
98 *aResult = nullptr;
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);
106 it.forget(aResult);
107 return NS_OK;
110 // nsIContent
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))) {
126 tf->SelectAll();
130 bool HTMLTextAreaElement::IsHTMLFocusable(IsFocusableFlags aFlags,
131 bool* aIsFocusable,
132 int32_t* aTabIndex) {
133 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
134 aFlags, aIsFocusable, aTabIndex)) {
135 return true;
138 // disabled textareas are not focusable
139 *aIsFocusable = !IsDisabled();
140 return false;
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 {
156 MOZ_ASSERT(mState);
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() {
168 MOZ_ASSERT(mState);
169 return mState->GetTextEditor();
172 TextEditor* HTMLTextAreaElement::GetTextEditorWithoutCreation() const {
173 MOZ_ASSERT(mState);
174 return mState->GetTextEditorWithoutCreation();
177 nsISelectionController* HTMLTextAreaElement::GetSelectionController() {
178 MOZ_ASSERT(mState);
179 return mState->GetSelectionController();
182 nsFrameSelection* HTMLTextAreaElement::GetConstFrameSelection() {
183 MOZ_ASSERT(mState);
184 return mState->GetConstFrameSelection();
187 nsresult HTMLTextAreaElement::BindToFrame(nsTextControlFrame* aFrame) {
188 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
189 MOZ_ASSERT(mState);
190 return mState->BindToFrame(aFrame);
193 void HTMLTextAreaElement::UnbindFromFrame(nsTextControlFrame* aFrame) {
194 MOZ_ASSERT(mState);
195 if (aFrame) {
196 mState->UnbindFromFrame(aFrame);
200 nsresult HTMLTextAreaElement::CreateEditor() {
201 MOZ_ASSERT(mState);
202 return mState->PrepareEditor();
205 void HTMLTextAreaElement::SetPreviewValue(const nsAString& aValue) {
206 MOZ_ASSERT(mState);
207 mState->SetPreviewText(aValue, true);
210 void HTMLTextAreaElement::GetPreviewValue(nsAString& aValue) {
211 MOZ_ASSERT(mState);
212 mState->GetPreviewText(aValue);
215 void HTMLTextAreaElement::EnablePreview() {
216 if (mIsPreviewEnabled) {
217 return;
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) {
230 MOZ_ASSERT(mState);
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;
243 return NS_OK;
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(
259 aValue,
260 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
261 ValueSetterOption::MoveCursorToEndIfValueChanged});
262 if (NS_WARN_IF(NS_FAILED(rv))) {
263 aError.Throw(rv);
264 return;
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) {
280 MOZ_ASSERT(mState);
282 bool previousValue = mValueChanged;
283 mValueChanged = aValueChanged;
284 if (!aValueChanged && !mState->IsEmpty()) {
285 mState->EmptyValue();
287 if (mValueChanged == previousValue) {
288 return;
290 UpdateTooLongValidityState();
291 UpdateTooShortValidityState();
292 UpdateValidityElementStates(true);
295 void HTMLTextAreaElement::SetLastValueChangeWasInteractive(
296 bool aWasInteractive) {
297 if (aWasInteractive == mLastValueChangeWasInteractive) {
298 return;
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,
312 fallible)) {
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) {
328 Reset();
330 if (NS_FAILED(rv)) {
331 aError.Throw(rv);
335 bool HTMLTextAreaElement::ParseAttribute(int32_t aNamespaceID,
336 nsAtom* aAttribute,
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);
346 return true;
347 } else if (aAttribute == nsGkAtoms::rows) {
348 aResult.ParseIntWithFallback(aValue, DEFAULT_ROWS_TEXTAREA);
349 return true;
350 } else if (aAttribute == nsGkAtoms::autocomplete) {
351 aResult.ParseAtomArray(aValue);
352 return true;
355 return TextControlElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
356 aMaybeScriptedPrincipal, aResult);
359 void HTMLTextAreaElement::MapAttributesIntoRule(
360 MappedDeclarationsBuilder& aBuilder) {
361 // wrap=off
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;
393 return retval;
396 NS_IMETHODIMP_(bool)
397 HTMLTextAreaElement::IsAttributeMapped(const nsAtom* aAttribute) const {
398 static const MappedAttributeEntry attributes[] = {{nsGkAtoms::wrap},
399 {nullptr}};
401 static const MappedAttributeEntry* const map[] = {
402 attributes,
403 sDivAlignAttributeMap,
404 sCommonAttributeMap,
407 return FindAttributeDependence(aAttribute, map);
410 nsMapRuleToAttributesFunc HTMLTextAreaElement::GetAttributeMappingFunction()
411 const {
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)) {
422 return;
425 // Don't dispatch a second select event if we are already handling
426 // one.
427 if (aVisitor.mEvent->mMessage == eFormSelect) {
428 if (mHandlingSelect) {
429 return;
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() {
452 nsString value;
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
458 if (mValueChanged) {
459 SetUserInteracted(true);
462 if (mFocusedValue.Equals(value)) {
463 return;
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);
479 return NS_OK;
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.
487 Reset();
490 if (!mInhibitStateRestoration) {
491 GenerateStateKey();
492 RestoreFormControlState();
496 mDoneAddingChildren = true;
499 // Controllers Methods
501 nsIControllers* HTMLTextAreaElement::GetControllers(ErrorResult& aError) {
502 if (!mControllers) {
503 mControllers = new nsXULControllers();
504 if (!mControllers) {
505 aError.Throw(NS_ERROR_FAILURE);
506 return nullptr;
509 RefPtr<nsBaseCommandController> commandController =
510 nsBaseCommandController::CreateEditorController();
511 if (!commandController) {
512 aError.Throw(NS_ERROR_FAILURE);
513 return nullptr;
516 mControllers->AppendController(commandController);
518 commandController = nsBaseCommandController::CreateEditingController();
519 if (!commandController) {
520 aError.Throw(NS_ERROR_FAILURE);
521 return nullptr;
524 mControllers->AppendController(commandController);
527 return mControllers;
530 nsresult HTMLTextAreaElement::GetControllers(nsIControllers** aResult) {
531 NS_ENSURE_ARG_POINTER(aResult);
533 ErrorResult error;
534 *aResult = GetControllers(error);
535 NS_IF_ADDREF(*aResult);
537 return error.StealNSResult();
540 uint32_t HTMLTextAreaElement::GetTextLength() {
541 nsAutoString val;
542 GetValue(val);
543 return val.Length();
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) {
554 MOZ_ASSERT(mState);
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) {
566 MOZ_ASSERT(mState);
567 mState->SetSelectionEnd(aSelectionEnd, aError);
570 void HTMLTextAreaElement::GetSelectionRange(uint32_t* aSelectionStart,
571 uint32_t* aSelectionEnd,
572 ErrorResult& aRv) {
573 MOZ_ASSERT(mState);
574 return mState->GetSelectionRange(aSelectionStart, aSelectionEnd, aRv);
577 void HTMLTextAreaElement::GetSelectionDirection(nsAString& aDirection,
578 ErrorResult& aError) {
579 MOZ_ASSERT(mState);
580 mState->GetSelectionDirectionString(aDirection, aError);
583 void HTMLTextAreaElement::SetSelectionDirection(const nsAString& aDirection,
584 ErrorResult& aError) {
585 MOZ_ASSERT(mState);
586 mState->SetSelectionDirection(aDirection, aError);
589 void HTMLTextAreaElement::SetSelectionRange(
590 uint32_t aSelectionStart, uint32_t aSelectionEnd,
591 const Optional<nsAString>& aDirection, ErrorResult& aError) {
592 MOZ_ASSERT(mState);
593 mState->SetSelectionRange(aSelectionStart, aSelectionEnd, aDirection, aError);
596 void HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement,
597 ErrorResult& aRv) {
598 MOZ_ASSERT(mState);
599 mState->SetRangeText(aReplacement, aRv);
602 void HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement,
603 uint32_t aStart, uint32_t aEnd,
604 SelectionMode aSelectMode,
605 ErrorResult& aRv) {
606 MOZ_ASSERT(mState);
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);
630 return NS_OK;
633 NS_IMETHODIMP
634 HTMLTextAreaElement::SubmitNamesValues(FormData* aFormData) {
636 // Get the name (if no name, no submit)
638 nsAutoString name;
639 GetAttr(nsGkAtoms::name, name);
640 if (name.IsEmpty()) {
641 return NS_OK;
645 // Get the value
647 nsAutoString value;
648 GetValueInternal(value, false);
651 // Submit name=value
653 const nsresult rv = aFormData->AddNameValuePair(name, value);
654 if (NS_FAILED(rv)) {
655 return rv;
658 // Submit dirname=dir
659 return SubmitDirnameDir(aFormData);
662 void HTMLTextAreaElement::SaveState() {
663 // Only save if value != defaultValue (bug 62713)
664 PresState* state = nullptr;
665 if (mValueChanged) {
666 state = GetPrimaryPresState();
667 if (state) {
668 nsAutoString value;
669 GetValueInternal(value, true);
671 if (NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
672 value, nsLinebreakConverter::eLinebreakPlatform,
673 nsLinebreakConverter::eLinebreakContent))) {
674 NS_ERROR("Converting linebreaks failed!");
675 return;
678 state->contentData() =
679 TextContentData(value, mLastValueChangeWasInteractive);
683 if (mDisabledChanged) {
684 if (!state) {
685 state = GetPrimaryPresState();
687 if (state) {
688 // We do not want to save the real disabled state but the disabled
689 // attribute.
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) {
700 ErrorResult rv;
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());
711 return false;
714 void HTMLTextAreaElement::UpdateValidityElementStates(bool aNotify) {
715 AutoStateChangeNotifier notifier(*this, aNotify);
716 RemoveStatesSilently(ElementState::VALIDITY_STATES);
717 if (!IsCandidateForConstraintValidation()) {
718 return;
720 ElementState state;
721 if (IsValid()) {
722 state |= ElementState::VALID;
723 if (mUserInteracted) {
724 state |= ElementState::USER_VALID;
726 } else {
727 state |= ElementState::INVALID;
728 if (mUserInteracted) {
729 state |= ElementState::USER_INVALID;
732 AddStatesSilently(state);
735 nsresult HTMLTextAreaElement::BindToTree(BindContext& aContext,
736 nsINode& aParent) {
737 nsresult rv =
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);
752 return rv;
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,
768 bool aNotify) {
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
807 // to 0.
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) {
825 self->Reset();
827 }));
831 void HTMLTextAreaElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
832 const nsAttrValue* aValue,
833 const nsAttrValue* aOldValue,
834 nsIPrincipal* aSubjectPrincipal,
835 bool aNotify) {
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
849 // state.
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);
896 nsAutoString value;
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.
901 if (NS_WARN_IF(
902 NS_FAILED(rv = MOZ_KnownLive(dest)->SetValueInternal(
903 value, {ValueSetterOption::SetValueChanged})))) {
904 return rv;
908 return NS_OK;
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)) {
921 return false;
924 int32_t maxLength = MaxLength();
926 // Maxlength of -1 means parsing error.
927 if (maxLength == -1) {
928 return false;
931 int32_t textLength = GetTextLength();
933 return textLength > maxLength;
936 bool HTMLTextAreaElement::IsTooShort() {
937 if (!mValueChanged || !mLastValueChangeWasInteractive ||
938 !HasAttr(nsGkAtoms::minlength)) {
939 return false;
942 int32_t minLength = MinLength();
944 // Minlength of -1 means parsing error.
945 if (minLength == -1) {
946 return false;
949 int32_t textLength = GetTextLength();
951 return textLength && textLength < minLength;
954 bool HTMLTextAreaElement::IsValueMissing() const {
955 if (!Required() || !IsMutable()) {
956 return false;
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) {
981 nsresult rv = NS_OK;
983 switch (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;
998 } break;
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,
1012 strTextLength);
1013 aValidationMessage = message;
1014 } break;
1015 case VALIDITY_STATE_VALUE_MISSING: {
1016 nsAutoString message;
1017 rv = nsContentUtils::GetMaybeLocalizedString(
1018 nsContentUtils::eDOM_PROPERTIES, "FormValidationValueMissing",
1019 OwnerDoc(), message);
1020 aValidationMessage = message;
1021 } break;
1022 default:
1023 rv =
1024 ConstraintValidation::GetValidationMessage(aValidationMessage, aType);
1027 return rv;
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) {
1039 return {};
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
1049 return 0;
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 {
1073 MOZ_ASSERT(mState);
1074 mState->GetValue(aValue, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
1077 void HTMLTextAreaElement::InitializeKeyboardEventListeners() {
1078 MOZ_ASSERT(mState);
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() {
1113 MOZ_ASSERT(mState);
1114 return mState->IsSelectionCached();
1117 void HTMLTextAreaElement::SetUserInteracted(bool aInteracted) {
1118 if (mUserInteracted == aInteracted) {
1119 return;
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
1128 // disabled state.
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