HTMLTextAreaElement.cpp (37890B)
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/. */ 6 7 #include "mozilla/dom/HTMLTextAreaElement.h" 8 9 #include "mozAutoDocUpdate.h" 10 #include "mozilla/AsyncEventDispatcher.h" 11 #include "mozilla/EventDispatcher.h" 12 #include "mozilla/MappedDeclarationsBuilder.h" 13 #include "mozilla/MouseEvents.h" 14 #include "mozilla/PresState.h" 15 #include "mozilla/TextControlState.h" 16 #include "mozilla/dom/Document.h" 17 #include "mozilla/dom/FormData.h" 18 #include "mozilla/dom/HTMLTextAreaElementBinding.h" 19 #include "nsAttrValueInlines.h" 20 #include "nsBaseCommandController.h" 21 #include "nsContentCreatorFunctions.h" 22 #include "nsError.h" 23 #include "nsFocusManager.h" 24 #include "nsIConstraintValidation.h" 25 #include "nsIControllers.h" 26 #include "nsIFormControl.h" 27 #include "nsIFrame.h" 28 #include "nsIMutationObserver.h" 29 #include "nsLayoutUtils.h" 30 #include "nsLinebreakConverter.h" 31 #include "nsPlainTextSerializer.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" 38 39 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(TextArea) 40 41 namespace mozilla::dom { 42 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 mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown), 52 mState(TextControlState::Construct(this)) { 53 AddMutationObserver(this); 54 55 // Set up our default state. By default we're enabled (since we're 56 // a control type that can be disabled but not actually disabled right now), 57 // optional, read-write, and valid. Also by default we don't have to show 58 // validity UI and so forth. 59 AddStatesSilently(ElementState::ENABLED | ElementState::OPTIONAL_ | 60 ElementState::READWRITE | ElementState::VALID | 61 ElementState::VALUE_EMPTY); 62 RemoveStatesSilently(ElementState::READONLY); 63 } 64 65 HTMLTextAreaElement::~HTMLTextAreaElement() { 66 mState->Destroy(); 67 mState = nullptr; 68 } 69 70 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLTextAreaElement) 71 72 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLTextAreaElement, 73 TextControlElement) 74 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity) 75 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers) 76 if (tmp->mState) { 77 tmp->mState->Traverse(cb); 78 } 79 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 80 81 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLTextAreaElement, 82 TextControlElement) 83 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity) 84 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers) 85 if (tmp->mState) { 86 tmp->mState->Unlink(); 87 } 88 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 89 90 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLTextAreaElement, 91 TextControlElement, 92 nsIMutationObserver, 93 nsIConstraintValidation) 94 95 // nsIDOMHTMLTextAreaElement 96 97 nsresult HTMLTextAreaElement::Clone(dom::NodeInfo* aNodeInfo, 98 nsINode** aResult) const { 99 *aResult = nullptr; 100 RefPtr<HTMLTextAreaElement> it = new (aNodeInfo->NodeInfoManager()) 101 HTMLTextAreaElement(do_AddRef(aNodeInfo)); 102 103 nsresult rv = const_cast<HTMLTextAreaElement*>(this)->CopyInnerTo(it); 104 NS_ENSURE_SUCCESS(rv, rv); 105 106 it->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive); 107 it.forget(aResult); 108 return NS_OK; 109 } 110 111 // nsIContent 112 113 void HTMLTextAreaElement::Select() { 114 if (FocusState() != FocusTristate::eUnfocusable) { 115 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { 116 fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL); 117 } 118 } 119 120 SetSelectionRange(0, UINT32_MAX, Optional<nsAString>(), IgnoreErrors()); 121 } 122 123 void HTMLTextAreaElement::SelectAll() { 124 // FIXME(emilio): Should we try to call Select(), which will avoid flushing? 125 if (nsTextControlFrame* tf = 126 do_QueryFrame(GetPrimaryFrame(FlushType::Frames))) { 127 tf->SelectAll(); 128 } 129 } 130 131 enum class Wrap { 132 Off, 133 Hard, 134 Soft, 135 }; 136 137 static Wrap WrapValue(const HTMLTextAreaElement& aElement) { 138 static mozilla::dom::Element::AttrValuesArray strings[] = { 139 nsGkAtoms::HARD, nsGkAtoms::OFF, nullptr}; 140 switch (aElement.FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::wrap, strings, 141 eIgnoreCase)) { 142 case 0: 143 return Wrap::Hard; 144 case 1: 145 return Wrap::Off; 146 default: 147 return Wrap::Soft; 148 } 149 } 150 151 bool HTMLTextAreaElement::IsHTMLFocusable(IsFocusableFlags aFlags, 152 bool* aIsFocusable, 153 int32_t* aTabIndex) { 154 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable( 155 aFlags, aIsFocusable, aTabIndex)) { 156 return true; 157 } 158 159 // disabled textareas are not focusable 160 *aIsFocusable = !IsDisabled(); 161 return false; 162 } 163 164 int32_t HTMLTextAreaElement::TabIndexDefault() { return 0; } 165 166 void HTMLTextAreaElement::GetType(nsAString& aType) { 167 aType.AssignLiteral("textarea"); 168 } 169 170 void HTMLTextAreaElement::GetValue(nsAString& aValue) { 171 GetValueInternal(aValue); 172 MOZ_ASSERT(aValue.FindChar(static_cast<char16_t>('\r')) == -1); 173 } 174 175 void HTMLTextAreaElement::GetValueInternal(nsAString& aValue) const { 176 MOZ_ASSERT(mState); 177 mState->GetValue(aValue, /* aForDisplay = */ true); 178 } 179 180 nsIEditor* HTMLTextAreaElement::GetEditorForBindings() { 181 if (!GetPrimaryFrame()) { 182 GetPrimaryFrame(FlushType::Frames); 183 } 184 return GetTextEditor(); 185 } 186 187 TextEditor* HTMLTextAreaElement::GetTextEditor() { 188 MOZ_ASSERT(mState); 189 return mState->GetTextEditor(); 190 } 191 192 TextEditor* HTMLTextAreaElement::GetExtantTextEditor() const { 193 MOZ_ASSERT(mState); 194 return mState->GetExtantTextEditor(); 195 } 196 197 nsISelectionController* HTMLTextAreaElement::GetSelectionController() { 198 MOZ_ASSERT(mState); 199 return mState->GetSelectionController(); 200 } 201 202 nsFrameSelection* HTMLTextAreaElement::GetIndependentFrameSelection() const { 203 MOZ_ASSERT(mState); 204 return mState->GetIndependentFrameSelection(); 205 } 206 207 nsresult HTMLTextAreaElement::BindToFrame(nsTextControlFrame* aFrame) { 208 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript()); 209 MOZ_ASSERT(mState); 210 return mState->BindToFrame(aFrame); 211 } 212 213 void HTMLTextAreaElement::UnbindFromFrame(nsTextControlFrame* aFrame) { 214 MOZ_ASSERT(mState); 215 if (aFrame) { 216 mState->UnbindFromFrame(aFrame); 217 } 218 } 219 220 nsresult HTMLTextAreaElement::CreateEditor() { 221 MOZ_ASSERT(mState); 222 return mState->PrepareEditor(); 223 } 224 225 void HTMLTextAreaElement::SetPreviewValue(const nsAString& aValue) { 226 MOZ_ASSERT(mState); 227 mState->SetPreviewText(aValue, true); 228 } 229 230 void HTMLTextAreaElement::GetPreviewValue(nsAString& aValue) { 231 MOZ_ASSERT(mState); 232 mState->GetPreviewText(aValue); 233 } 234 235 void HTMLTextAreaElement::EnablePreview() { 236 if (mIsPreviewEnabled) { 237 return; 238 } 239 240 mIsPreviewEnabled = true; 241 // Reconstruct the frame to append an anonymous preview node 242 nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0}, 243 nsChangeHint_ReconstructFrame); 244 } 245 246 bool HTMLTextAreaElement::IsPreviewEnabled() { return mIsPreviewEnabled; } 247 248 nsresult HTMLTextAreaElement::SetValueInternal( 249 const nsAString& aValue, const ValueSetterOptions& aOptions) { 250 MOZ_ASSERT(mState); 251 252 // Need to set the value changed flag here if our value has in fact changed 253 // (i.e. if ValueSetterOption::SetValueChanged is in aOptions), so that 254 // retrieves the correct value if needed. 255 if (aOptions.contains(ValueSetterOption::SetValueChanged)) { 256 SetValueChanged(true); 257 } 258 259 if (!mState->SetValue(aValue, aOptions)) { 260 return NS_ERROR_OUT_OF_MEMORY; 261 } 262 263 return NS_OK; 264 } 265 266 void HTMLTextAreaElement::SetValue(const nsAString& aValue, 267 ErrorResult& aError) { 268 // If the value has been set by a script, we basically want to keep the 269 // current change event state. If the element is ready to fire a change 270 // event, we should keep it that way. Otherwise, we should make sure the 271 // element will not fire any event because of the script interaction. 272 // 273 // NOTE: this is currently quite expensive work (too much string 274 // manipulation). We should probably optimize that. 275 nsAutoString currentValue; 276 GetValueInternal(currentValue); 277 278 nsresult rv = SetValueInternal( 279 aValue, 280 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged, 281 ValueSetterOption::MoveCursorToEndIfValueChanged}); 282 if (NS_WARN_IF(NS_FAILED(rv))) { 283 aError.Throw(rv); 284 return; 285 } 286 287 if (mFocusedValue.Equals(currentValue)) { 288 GetValueInternal(mFocusedValue); 289 } 290 } 291 292 void HTMLTextAreaElement::SetUserInput(const nsAString& aValue, 293 nsIPrincipal& aSubjectPrincipal) { 294 SetValueInternal(aValue, {ValueSetterOption::BySetUserInputAPI, 295 ValueSetterOption::SetValueChanged, 296 ValueSetterOption::MoveCursorToEndIfValueChanged}); 297 } 298 299 void HTMLTextAreaElement::SetValueChanged(bool aValueChanged) { 300 MOZ_ASSERT(mState); 301 302 bool previousValue = mValueChanged; 303 mValueChanged = aValueChanged; 304 if (!aValueChanged && !mState->IsEmpty()) { 305 mState->EmptyValue(); 306 } 307 if (mValueChanged == previousValue) { 308 return; 309 } 310 UpdateTooLongValidityState(); 311 UpdateTooShortValidityState(); 312 UpdateValidityElementStates(true); 313 } 314 315 void HTMLTextAreaElement::SetLastValueChangeWasInteractive( 316 bool aWasInteractive) { 317 if (aWasInteractive == mLastValueChangeWasInteractive) { 318 return; 319 } 320 mLastValueChangeWasInteractive = aWasInteractive; 321 const bool wasValid = IsValid(); 322 UpdateTooLongValidityState(); 323 UpdateTooShortValidityState(); 324 if (wasValid != IsValid()) { 325 UpdateValidityElementStates(true); 326 } 327 } 328 329 void HTMLTextAreaElement::GetDefaultValue(nsAString& aDefaultValue, 330 ErrorResult& aError) const { 331 if (!nsContentUtils::GetNodeTextContent(this, false, aDefaultValue, 332 fallible)) { 333 aError.Throw(NS_ERROR_OUT_OF_MEMORY); 334 } 335 } 336 337 void HTMLTextAreaElement::SetDefaultValue(const nsAString& aDefaultValue, 338 ErrorResult& aError) { 339 // setting the value of an textarea element using `.defaultValue = "foo"` 340 // must be interpreted as a two-step operation: 341 // 1. clearing all child nodes 342 // 2. adding a new text node with the new content 343 // Step 1 must therefore collapse the Selection to 0. 344 // Calling `SetNodeTextContent()` with an empty string will do that for us. 345 nsContentUtils::SetNodeTextContent(this, EmptyString(), true); 346 nsresult rv = nsContentUtils::SetNodeTextContent(this, aDefaultValue, true); 347 if (NS_SUCCEEDED(rv) && !mValueChanged) { 348 Reset(); 349 } 350 if (NS_FAILED(rv)) { 351 aError.Throw(rv); 352 } 353 } 354 355 bool HTMLTextAreaElement::ParseAttribute(int32_t aNamespaceID, 356 nsAtom* aAttribute, 357 const nsAString& aValue, 358 nsIPrincipal* aMaybeScriptedPrincipal, 359 nsAttrValue& aResult) { 360 if (aNamespaceID == kNameSpaceID_None) { 361 if (aAttribute == nsGkAtoms::maxlength || 362 aAttribute == nsGkAtoms::minlength) { 363 return aResult.ParseNonNegativeIntValue(aValue); 364 } else if (aAttribute == nsGkAtoms::cols) { 365 aResult.ParseIntWithFallback(aValue, DEFAULT_COLS); 366 return true; 367 } else if (aAttribute == nsGkAtoms::rows) { 368 aResult.ParseIntWithFallback(aValue, DEFAULT_ROWS_TEXTAREA); 369 return true; 370 } else if (aAttribute == nsGkAtoms::autocomplete) { 371 aResult.ParseAtomArray(aValue); 372 return true; 373 } 374 } 375 return TextControlElement::ParseAttribute(aNamespaceID, aAttribute, aValue, 376 aMaybeScriptedPrincipal, aResult); 377 } 378 379 void HTMLTextAreaElement::MapAttributesIntoRule( 380 MappedDeclarationsBuilder& aBuilder) { 381 // wrap=off 382 const nsAttrValue* value = aBuilder.GetAttr(nsGkAtoms::wrap); 383 if (value && 384 (value->Type() == nsAttrValue::eString || 385 value->Type() == nsAttrValue::eAtom) && 386 value->Equals(nsGkAtoms::OFF, eIgnoreCase)) { 387 // Equivalent to expanding `white-space; pre` 388 aBuilder.SetKeywordValue(eCSSProperty_white_space_collapse, 389 StyleWhiteSpaceCollapse::Preserve); 390 aBuilder.SetKeywordValue(eCSSProperty_text_wrap_mode, 391 StyleTextWrapMode::Nowrap); 392 } 393 394 nsGenericHTMLFormControlElementWithState::MapDivAlignAttributeInto(aBuilder); 395 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder); 396 } 397 398 nsChangeHint HTMLTextAreaElement::GetAttributeChangeHint( 399 const nsAtom* aAttribute, AttrModType aModType) const { 400 nsChangeHint retval = 401 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint( 402 aAttribute, aModType); 403 404 const bool isAdditionOrRemoval = IsAdditionOrRemoval(aModType); 405 if (aAttribute == nsGkAtoms::rows || aAttribute == nsGkAtoms::cols) { 406 retval |= NS_STYLE_HINT_REFLOW; 407 } else if (aAttribute == nsGkAtoms::wrap) { 408 retval |= nsChangeHint_ReconstructFrame; 409 } else if (aAttribute == nsGkAtoms::placeholder && isAdditionOrRemoval) { 410 retval |= nsChangeHint_ReconstructFrame; 411 } 412 return retval; 413 } 414 415 NS_IMETHODIMP_(bool) 416 HTMLTextAreaElement::IsAttributeMapped(const nsAtom* aAttribute) const { 417 static const MappedAttributeEntry attributes[] = {{nsGkAtoms::wrap}, 418 {nullptr}}; 419 420 static const MappedAttributeEntry* const map[] = { 421 attributes, 422 sDivAlignAttributeMap, 423 sCommonAttributeMap, 424 }; 425 426 return FindAttributeDependence(aAttribute, map); 427 } 428 429 nsMapRuleToAttributesFunc HTMLTextAreaElement::GetAttributeMappingFunction() 430 const { 431 return &MapAttributesIntoRule; 432 } 433 434 bool HTMLTextAreaElement::IsDisabledForEvents(WidgetEvent* aEvent) { 435 return IsElementDisabledForEvents(aEvent, GetPrimaryFrame()); 436 } 437 438 void HTMLTextAreaElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 439 aVisitor.mCanHandle = false; 440 if (IsDisabledForEvents(aVisitor.mEvent)) { 441 return; 442 } 443 444 // Don't dispatch a second select event if we are already handling 445 // one. 446 if (aVisitor.mEvent->mMessage == eFormSelect) { 447 if (mHandlingSelect) { 448 return; 449 } 450 mHandlingSelect = true; 451 } 452 453 if (aVisitor.mEvent->mMessage == eBlur) { 454 // Set mWantsPreHandleEvent and fire change event in PreHandleEvent to 455 // prevent it breaks event target chain creation. 456 aVisitor.mWantsPreHandleEvent = true; 457 } 458 459 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor); 460 } 461 462 nsresult HTMLTextAreaElement::PreHandleEvent(EventChainVisitor& aVisitor) { 463 if (aVisitor.mEvent->mMessage == eBlur) { 464 // Fire onchange (if necessary), before we do the blur, bug 370521. 465 FireChangeEventIfNeeded(); 466 } 467 return nsGenericHTMLFormControlElementWithState::PreHandleEvent(aVisitor); 468 } 469 470 void HTMLTextAreaElement::FireChangeEventIfNeeded() { 471 nsString value; 472 GetValueInternal(value); 473 474 // NOTE(emilio): This is not quite on the spec, but matches <input>, see 475 // https://github.com/whatwg/html/issues/10011 and 476 // https://github.com/whatwg/html/issues/10013 477 if (mValueChanged) { 478 SetUserInteracted(true); 479 } 480 481 if (mFocusedValue.Equals(value)) { 482 return; 483 } 484 485 // Dispatch the change event. 486 mFocusedValue = value; 487 nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u"change"_ns, 488 CanBubble::eYes, Cancelable::eNo); 489 } 490 491 nsresult HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { 492 if (aVisitor.mEvent->mMessage == eFormSelect) { 493 mHandlingSelect = false; 494 } 495 if (aVisitor.mEvent->mMessage == eFocus) { 496 GetValueInternal(mFocusedValue); 497 } 498 return NS_OK; 499 } 500 501 void HTMLTextAreaElement::DoneAddingChildren(bool aHaveNotified) { 502 if (!mValueChanged) { 503 if (!mDoneAddingChildren) { 504 // Reset now that we're done adding children if the content sink tried to 505 // sneak some text in without calling AppendChildTo. 506 Reset(); 507 } 508 509 if (!mInhibitStateRestoration) { 510 GenerateStateKey(); 511 RestoreFormControlState(); 512 } 513 } 514 515 mDoneAddingChildren = true; 516 } 517 518 // Controllers Methods 519 520 nsIControllers* HTMLTextAreaElement::GetControllers(ErrorResult& aError) { 521 if (!mControllers) { 522 mControllers = new nsXULControllers(); 523 if (!mControllers) { 524 aError.Throw(NS_ERROR_FAILURE); 525 return nullptr; 526 } 527 528 RefPtr<nsBaseCommandController> commandController = 529 nsBaseCommandController::CreateEditorController(); 530 if (!commandController) { 531 aError.Throw(NS_ERROR_FAILURE); 532 return nullptr; 533 } 534 535 mControllers->AppendController(commandController); 536 537 commandController = nsBaseCommandController::CreateEditingController(); 538 if (!commandController) { 539 aError.Throw(NS_ERROR_FAILURE); 540 return nullptr; 541 } 542 543 mControllers->AppendController(commandController); 544 } 545 546 return GetExtantControllers(); 547 } 548 549 nsresult HTMLTextAreaElement::GetControllers(nsIControllers** aResult) { 550 NS_ENSURE_ARG_POINTER(aResult); 551 552 ErrorResult error; 553 *aResult = GetControllers(error); 554 NS_IF_ADDREF(*aResult); 555 556 return error.StealNSResult(); 557 } 558 559 uint32_t HTMLTextAreaElement::GetTextLength() { 560 nsAutoString val; 561 GetValue(val); 562 return val.Length(); 563 } 564 565 Nullable<uint32_t> HTMLTextAreaElement::GetSelectionStart(ErrorResult& aError) { 566 uint32_t selStart, selEnd; 567 GetSelectionRange(&selStart, &selEnd, aError); 568 return Nullable<uint32_t>(selStart); 569 } 570 571 void HTMLTextAreaElement::SetSelectionStart( 572 const Nullable<uint32_t>& aSelectionStart, ErrorResult& aError) { 573 MOZ_ASSERT(mState); 574 mState->SetSelectionStart(aSelectionStart, aError); 575 } 576 577 Nullable<uint32_t> HTMLTextAreaElement::GetSelectionEnd(ErrorResult& aError) { 578 uint32_t selStart, selEnd; 579 GetSelectionRange(&selStart, &selEnd, aError); 580 return Nullable<uint32_t>(selEnd); 581 } 582 583 void HTMLTextAreaElement::SetSelectionEnd( 584 const Nullable<uint32_t>& aSelectionEnd, ErrorResult& aError) { 585 MOZ_ASSERT(mState); 586 mState->SetSelectionEnd(aSelectionEnd, aError); 587 } 588 589 void HTMLTextAreaElement::GetSelectionRange(uint32_t* aSelectionStart, 590 uint32_t* aSelectionEnd, 591 ErrorResult& aRv) { 592 MOZ_ASSERT(mState); 593 return mState->GetSelectionRange(aSelectionStart, aSelectionEnd, aRv); 594 } 595 596 void HTMLTextAreaElement::GetSelectionDirection(nsAString& aDirection, 597 ErrorResult& aError) { 598 MOZ_ASSERT(mState); 599 mState->GetSelectionDirectionString(aDirection, aError); 600 } 601 602 void HTMLTextAreaElement::SetSelectionDirection(const nsAString& aDirection, 603 ErrorResult& aError) { 604 MOZ_ASSERT(mState); 605 mState->SetSelectionDirection(aDirection, aError); 606 } 607 608 void HTMLTextAreaElement::SetSelectionRange( 609 uint32_t aSelectionStart, uint32_t aSelectionEnd, 610 const Optional<nsAString>& aDirection, ErrorResult& aError) { 611 MOZ_ASSERT(mState); 612 mState->SetSelectionRange(aSelectionStart, aSelectionEnd, aDirection, aError); 613 } 614 615 void HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement, 616 ErrorResult& aRv) { 617 MOZ_ASSERT(mState); 618 mState->SetRangeText(aReplacement, aRv); 619 } 620 621 void HTMLTextAreaElement::SetRangeText(const nsAString& aReplacement, 622 uint32_t aStart, uint32_t aEnd, 623 SelectionMode aSelectMode, 624 ErrorResult& aRv) { 625 MOZ_ASSERT(mState); 626 mState->SetRangeText(aReplacement, aStart, aEnd, aSelectMode, aRv); 627 } 628 629 void HTMLTextAreaElement::GetValueFromSetRangeText(nsAString& aValue) { 630 GetValueInternal(aValue); 631 } 632 633 nsresult HTMLTextAreaElement::SetValueFromSetRangeText( 634 const nsAString& aValue) { 635 return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI, 636 ValueSetterOption::BySetRangeTextAPI, 637 ValueSetterOption::SetValueChanged}); 638 } 639 640 nsresult HTMLTextAreaElement::Reset() { 641 nsAutoString resetVal; 642 GetDefaultValue(resetVal, IgnoreErrors()); 643 SetValueChanged(false); 644 SetUserInteracted(false); 645 646 nsresult rv = SetValueInternal(resetVal, ValueSetterOption::ByInternalAPI); 647 NS_ENSURE_SUCCESS(rv, rv); 648 649 return NS_OK; 650 } 651 652 NS_IMETHODIMP 653 HTMLTextAreaElement::SubmitNamesValues(FormData* aFormData) { 654 // 655 // Get the name (if no name, no submit) 656 // 657 nsAutoString name; 658 GetAttr(nsGkAtoms::name, name); 659 if (name.IsEmpty()) { 660 return NS_OK; 661 } 662 663 // Get the value 664 nsAutoString value; 665 GetValueInternal(value); 666 if (WrapValue(*this) == Wrap::Hard) { 667 if (auto cols = GetWrapCols(); cols > 0) { 668 int32_t flags = nsIDocumentEncoder::OutputLFLineBreak | 669 nsIDocumentEncoder::OutputPreformatted | 670 nsIDocumentEncoder::OutputPersistNBSP | 671 nsIDocumentEncoder::OutputBodyOnly | 672 nsIDocumentEncoder::OutputWrap; 673 nsPlainTextSerializer::HardWrapString(value, cols, flags); 674 } 675 } 676 677 // Submit name=value 678 const nsresult rv = aFormData->AddNameValuePair(name, value); 679 if (NS_FAILED(rv)) { 680 return rv; 681 } 682 683 // Submit dirname=dir 684 return SubmitDirnameDir(aFormData); 685 } 686 687 void HTMLTextAreaElement::SaveState() { 688 // Only save if value != defaultValue (bug 62713) 689 PresState* state = nullptr; 690 if (mValueChanged) { 691 state = GetPrimaryPresState(); 692 if (state) { 693 nsAutoString value; 694 GetValueInternal(value); 695 696 if (NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks( 697 value, nsLinebreakConverter::eLinebreakPlatform, 698 nsLinebreakConverter::eLinebreakContent))) { 699 NS_ERROR("Converting linebreaks failed!"); 700 return; 701 } 702 703 state->contentData() = 704 TextContentData(value, mLastValueChangeWasInteractive); 705 } 706 } 707 708 if (mDisabledChanged) { 709 if (!state) { 710 state = GetPrimaryPresState(); 711 } 712 if (state) { 713 // We do not want to save the real disabled state but the disabled 714 // attribute. 715 state->disabled() = HasAttr(nsGkAtoms::disabled); 716 state->disabledSet() = true; 717 } 718 } 719 } 720 721 bool HTMLTextAreaElement::RestoreState(PresState* aState) { 722 const PresContentData& state = aState->contentData(); 723 724 if (state.type() == PresContentData::TTextContentData) { 725 ErrorResult rv; 726 SetValue(state.get_TextContentData().value(), rv); 727 if (NS_WARN_IF(rv.Failed())) { 728 rv.SuppressException(); 729 return false; 730 } 731 if (state.get_TextContentData().lastValueChangeWasInteractive()) { 732 SetLastValueChangeWasInteractive(true); 733 } 734 } 735 if (aState->disabledSet() && !aState->disabled()) { 736 SetDisabled(false, IgnoreErrors()); 737 } 738 739 return false; 740 } 741 742 void HTMLTextAreaElement::UpdateValidityElementStates(bool aNotify) { 743 AutoStateChangeNotifier notifier(*this, aNotify); 744 RemoveStatesSilently(ElementState::VALIDITY_STATES); 745 if (!IsCandidateForConstraintValidation()) { 746 return; 747 } 748 ElementState state; 749 if (IsValid()) { 750 state |= ElementState::VALID; 751 if (mUserInteracted) { 752 state |= ElementState::USER_VALID; 753 } 754 } else { 755 state |= ElementState::INVALID; 756 if (mUserInteracted) { 757 state |= ElementState::USER_INVALID; 758 } 759 } 760 AddStatesSilently(state); 761 } 762 763 nsresult HTMLTextAreaElement::BindToTree(BindContext& aContext, 764 nsINode& aParent) { 765 nsresult rv = 766 nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent); 767 NS_ENSURE_SUCCESS(rv, rv); 768 769 // Set direction based on value if dir=auto 770 ResetDirFormAssociatedElement(this, false, HasDirAuto()); 771 772 // If there is a disabled fieldset in the parent chain, the element is now 773 // barred from constraint validation and can't suffer from value missing. 774 UpdateValueMissingValidityState(); 775 UpdateBarredFromConstraintValidation(); 776 777 // And now make sure our state is up to date 778 UpdateValidityElementStates(false); 779 780 return rv; 781 } 782 783 void HTMLTextAreaElement::UnbindFromTree(UnbindContext& aContext) { 784 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext); 785 786 // We might be no longer disabled because of parent chain changed. 787 UpdateValueMissingValidityState(); 788 UpdateBarredFromConstraintValidation(); 789 790 // And now make sure our state is up to date 791 UpdateValidityElementStates(false); 792 } 793 794 void HTMLTextAreaElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, 795 const nsAttrValue* aValue, 796 bool aNotify) { 797 if (aNotify && aName == nsGkAtoms::disabled && 798 aNameSpaceID == kNameSpaceID_None) { 799 mDisabledChanged = true; 800 } 801 802 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr( 803 aNameSpaceID, aName, aValue, aNotify); 804 } 805 806 void HTMLTextAreaElement::CharacterDataChanged(nsIContent* aContent, 807 const CharacterDataChangeInfo&) { 808 ContentChanged(aContent); 809 } 810 811 void HTMLTextAreaElement::ContentAppended(nsIContent* aFirstNewContent, 812 const ContentAppendInfo&) { 813 ContentChanged(aFirstNewContent); 814 } 815 816 void HTMLTextAreaElement::ContentInserted(nsIContent* aChild, 817 const ContentInsertInfo&) { 818 ContentChanged(aChild); 819 } 820 821 void HTMLTextAreaElement::ContentWillBeRemoved(nsIContent* aChild, 822 const ContentRemoveInfo& aInfo) { 823 if (mValueChanged || !mDoneAddingChildren || 824 (aInfo.mBatchRemovalState && !aInfo.mBatchRemovalState->mIsFirst) || 825 !nsContentUtils::IsInSameAnonymousTree(this, aChild)) { 826 return; 827 } 828 if (mState->IsSelectionCached()) { 829 // Collapse the selection when removing nodes if necessary, see bug 1818686. 830 auto& props = mState->GetSelectionProperties(); 831 props.CollapseToStart(); 832 } 833 nsContentUtils::AddScriptRunner( 834 NewRunnableMethod("HTMLTextAreaElement::ResetIfUnchanged", this, 835 &HTMLTextAreaElement::ResetIfUnchanged)); 836 } 837 838 void HTMLTextAreaElement::ContentChanged(nsIContent* aContent) { 839 if (mValueChanged || !mDoneAddingChildren || 840 !nsContentUtils::IsInSameAnonymousTree(this, aContent)) { 841 return; 842 } 843 // We should wait all ranges finish handling the mutation before updating 844 // the anonymous subtree with a call of Reset. 845 nsContentUtils::AddScriptRunner( 846 NewRunnableMethod("HTMLTextAreaElement::ResetIfUnchanged", this, 847 &HTMLTextAreaElement::ResetIfUnchanged)); 848 } 849 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); 863 } 864 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); 870 } 871 872 if (aName == nsGkAtoms::readonly && !!aValue != !!aOldValue) { 873 UpdateReadOnlyState(aNotify); 874 } 875 876 UpdateValueMissingValidityState(); 877 878 // This *has* to be called *after* validity has changed. 879 if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) { 880 UpdateBarredFromConstraintValidation(); 881 } 882 UpdateValidityElementStates(aNotify); 883 } else if (aName == nsGkAtoms::autocomplete) { 884 // Clear the cached @autocomplete attribute state. 885 mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown; 886 mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown; 887 } else if (aName == nsGkAtoms::maxlength) { 888 UpdateTooLongValidityState(); 889 UpdateValidityElementStates(aNotify); 890 } else if (aName == nsGkAtoms::minlength) { 891 UpdateTooShortValidityState(); 892 UpdateValidityElementStates(aNotify); 893 } else if (aName == nsGkAtoms::placeholder) { 894 if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) { 895 f->PlaceholderChanged(aOldValue, aValue); 896 } 897 UpdatePlaceholderShownState(); 898 } else if (aName == nsGkAtoms::dir && aValue && 899 aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) { 900 ResetDirFormAssociatedElement(this, aNotify, true); 901 } 902 } 903 904 return nsGenericHTMLFormControlElementWithState::AfterSetAttr( 905 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); 906 } 907 908 nsresult HTMLTextAreaElement::CopyInnerTo(Element* aDest) { 909 nsresult rv = nsGenericHTMLFormControlElementWithState::CopyInnerTo(aDest); 910 NS_ENSURE_SUCCESS(rv, rv); 911 912 if (mValueChanged || aDest->OwnerDoc()->IsStaticDocument()) { 913 // Set our value on the clone. 914 auto* dest = static_cast<HTMLTextAreaElement*>(aDest); 915 916 nsAutoString value; 917 GetValueInternal(value); 918 919 // SetValueInternal handles setting mValueChanged for us. dest is a fresh 920 // element so setting its value can't really run script. 921 if (NS_WARN_IF( 922 NS_FAILED(rv = MOZ_KnownLive(dest)->SetValueInternal( 923 value, {ValueSetterOption::SetValueChanged})))) { 924 return rv; 925 } 926 } 927 928 return NS_OK; 929 } 930 931 bool HTMLTextAreaElement::IsMutable() const { return !IsDisabledOrReadOnly(); } 932 933 void HTMLTextAreaElement::SetCustomValidity(const nsAString& aError) { 934 ConstraintValidation::SetCustomValidity(aError); 935 UpdateValidityElementStates(true); 936 } 937 938 bool HTMLTextAreaElement::IsTooLong() { 939 if (!mValueChanged || !mLastValueChangeWasInteractive || 940 !HasAttr(nsGkAtoms::maxlength)) { 941 return false; 942 } 943 944 int32_t maxLength = MaxLength(); 945 946 // Maxlength of -1 means parsing error. 947 if (maxLength == -1) { 948 return false; 949 } 950 951 int32_t textLength = GetTextLength(); 952 953 return textLength > maxLength; 954 } 955 956 bool HTMLTextAreaElement::IsTooShort() { 957 if (!mValueChanged || !mLastValueChangeWasInteractive || 958 !HasAttr(nsGkAtoms::minlength)) { 959 return false; 960 } 961 962 int32_t minLength = MinLength(); 963 964 // Minlength of -1 means parsing error. 965 if (minLength == -1) { 966 return false; 967 } 968 969 int32_t textLength = GetTextLength(); 970 971 return textLength && textLength < minLength; 972 } 973 974 bool HTMLTextAreaElement::IsValueMissing() const { 975 if (!Required() || !IsMutable()) { 976 return false; 977 } 978 return IsValueEmpty(); 979 } 980 981 void HTMLTextAreaElement::UpdateTooLongValidityState() { 982 SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong()); 983 } 984 985 void HTMLTextAreaElement::UpdateTooShortValidityState() { 986 SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort()); 987 } 988 989 void HTMLTextAreaElement::UpdateValueMissingValidityState() { 990 SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing()); 991 } 992 993 void HTMLTextAreaElement::UpdateBarredFromConstraintValidation() { 994 SetBarredFromConstraintValidation( 995 HasAttr(nsGkAtoms::readonly) || 996 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled()); 997 } 998 999 nsresult HTMLTextAreaElement::GetValidationMessage( 1000 nsAString& aValidationMessage, ValidityStateType aType) { 1001 nsresult rv = NS_OK; 1002 1003 switch (aType) { 1004 case VALIDITY_STATE_TOO_LONG: { 1005 nsAutoString message; 1006 int32_t maxLength = MaxLength(); 1007 int32_t textLength = GetTextLength(); 1008 nsAutoString strMaxLength; 1009 nsAutoString strTextLength; 1010 1011 strMaxLength.AppendInt(maxLength); 1012 strTextLength.AppendInt(textLength); 1013 1014 rv = nsContentUtils::FormatMaybeLocalizedString( 1015 message, nsContentUtils::eDOM_PROPERTIES, "FormValidationTextTooLong", 1016 OwnerDoc(), strMaxLength, strTextLength); 1017 aValidationMessage = message; 1018 } break; 1019 case VALIDITY_STATE_TOO_SHORT: { 1020 nsAutoString message; 1021 int32_t minLength = MinLength(); 1022 int32_t textLength = GetTextLength(); 1023 nsAutoString strMinLength; 1024 nsAutoString strTextLength; 1025 1026 strMinLength.AppendInt(minLength); 1027 strTextLength.AppendInt(textLength); 1028 1029 rv = nsContentUtils::FormatMaybeLocalizedString( 1030 message, nsContentUtils::eDOM_PROPERTIES, 1031 "FormValidationTextTooShort", OwnerDoc(), strMinLength, 1032 strTextLength); 1033 aValidationMessage = message; 1034 } break; 1035 case VALIDITY_STATE_VALUE_MISSING: { 1036 nsAutoString message; 1037 rv = nsContentUtils::GetMaybeLocalizedString( 1038 nsContentUtils::eDOM_PROPERTIES, "FormValidationValueMissing", 1039 OwnerDoc(), message); 1040 aValidationMessage = message; 1041 } break; 1042 default: 1043 rv = 1044 ConstraintValidation::GetValidationMessage(aValidationMessage, aType); 1045 } 1046 1047 return rv; 1048 } 1049 1050 bool HTMLTextAreaElement::IsSingleLineTextControl() const { return false; } 1051 1052 bool HTMLTextAreaElement::IsTextArea() const { return true; } 1053 1054 bool HTMLTextAreaElement::IsPasswordTextControl() const { return false; } 1055 1056 Maybe<int32_t> HTMLTextAreaElement::GetCols() { 1057 const nsAttrValue* value = GetParsedAttr(nsGkAtoms::cols); 1058 if (!value || value->Type() != nsAttrValue::eInteger) { 1059 return {}; 1060 } 1061 return Some(value->GetIntegerValue()); 1062 } 1063 1064 int32_t HTMLTextAreaElement::GetWrapCols() { 1065 if (WrapValue(*this) == Wrap::Off) { 1066 return 0; 1067 } 1068 // Otherwise we just wrap at the given number of columns 1069 return GetColsOrDefault(); 1070 } 1071 1072 int32_t HTMLTextAreaElement::GetRows() { 1073 const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::rows); 1074 if (attr && attr->Type() == nsAttrValue::eInteger) { 1075 int32_t rows = attr->GetIntegerValue(); 1076 return (rows <= 0) ? DEFAULT_ROWS_TEXTAREA : rows; 1077 } 1078 1079 return DEFAULT_ROWS_TEXTAREA; 1080 } 1081 1082 void HTMLTextAreaElement::GetDefaultValueFromContent(nsAString& aValue, bool) { 1083 GetDefaultValue(aValue, IgnoreErrors()); 1084 } 1085 1086 bool HTMLTextAreaElement::ValueChanged() const { return mValueChanged; } 1087 1088 void HTMLTextAreaElement::GetTextEditorValue(nsAString& aValue) const { 1089 MOZ_ASSERT(mState); 1090 mState->GetValue(aValue, /* aForDisplay = */ true); 1091 } 1092 1093 void HTMLTextAreaElement::InitializeKeyboardEventListeners() { 1094 MOZ_ASSERT(mState); 1095 mState->InitializeKeyboardEventListeners(); 1096 } 1097 1098 void HTMLTextAreaElement::UpdatePlaceholderShownState() { 1099 SetStates(ElementState::PLACEHOLDER_SHOWN, 1100 IsValueEmpty() && HasAttr(nsGkAtoms::placeholder)); 1101 } 1102 1103 void HTMLTextAreaElement::OnValueChanged(ValueChangeKind aKind, 1104 bool aNewValueEmpty, 1105 const nsAString* aKnownNewValue) { 1106 if (aKind != ValueChangeKind::Internal) { 1107 mLastValueChangeWasInteractive = aKind == ValueChangeKind::UserInteraction; 1108 } 1109 1110 if (aNewValueEmpty != IsValueEmpty()) { 1111 SetStates(ElementState::VALUE_EMPTY, aNewValueEmpty); 1112 UpdatePlaceholderShownState(); 1113 } 1114 1115 // Update the validity state 1116 const bool validBefore = IsValid(); 1117 UpdateTooLongValidityState(); 1118 UpdateTooShortValidityState(); 1119 UpdateValueMissingValidityState(); 1120 1121 ResetDirFormAssociatedElement(this, true, HasDirAuto(), aKnownNewValue); 1122 1123 if (validBefore != IsValid()) { 1124 UpdateValidityElementStates(true); 1125 } 1126 } 1127 1128 bool HTMLTextAreaElement::HasCachedSelection() { 1129 MOZ_ASSERT(mState); 1130 return mState->IsSelectionCached(); 1131 } 1132 1133 void HTMLTextAreaElement::SetUserInteracted(bool aInteracted) { 1134 if (mUserInteracted == aInteracted) { 1135 return; 1136 } 1137 mUserInteracted = aInteracted; 1138 UpdateValidityElementStates(true); 1139 } 1140 1141 void HTMLTextAreaElement::FieldSetDisabledChanged(bool aNotify) { 1142 // This *has* to be called before UpdateBarredFromConstraintValidation and 1143 // UpdateValueMissingValidityState because these two functions depend on our 1144 // disabled state. 1145 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify); 1146 1147 UpdateValueMissingValidityState(); 1148 UpdateBarredFromConstraintValidation(); 1149 UpdateValidityElementStates(true); 1150 } 1151 1152 JSObject* HTMLTextAreaElement::WrapNode(JSContext* aCx, 1153 JS::Handle<JSObject*> aGivenProto) { 1154 return HTMLTextAreaElement_Binding::Wrap(aCx, this, aGivenProto); 1155 } 1156 1157 void HTMLTextAreaElement::GetAutocomplete(nsAString& aValue) { 1158 aValue.Truncate(); 1159 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete); 1160 1161 mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute( 1162 attributeVal, aValue, mAutocompleteAttrState); 1163 } 1164 1165 void HTMLTextAreaElement::GetAutocompleteInfo(AutocompleteInfo& aInfo) { 1166 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete); 1167 mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute( 1168 attributeVal, aInfo, mAutocompleteInfoState, true); 1169 } 1170 1171 } // namespace mozilla::dom