HTMLSelectElement.cpp (54677B)
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/HTMLSelectElement.h" 8 9 #include "mozAutoDocUpdate.h" 10 #include "mozilla/BasicEvents.h" 11 #include "mozilla/EventDispatcher.h" 12 #include "mozilla/MappedDeclarationsBuilder.h" 13 #include "mozilla/Maybe.h" 14 #include "mozilla/PresState.h" 15 #include "mozilla/dom/Document.h" 16 #include "mozilla/dom/Element.h" 17 #include "mozilla/dom/FormData.h" 18 #include "mozilla/dom/HTMLOptGroupElement.h" 19 #include "mozilla/dom/HTMLOptionElement.h" 20 #include "mozilla/dom/HTMLSelectElementBinding.h" 21 #include "mozilla/dom/UnionTypes.h" 22 #include "mozilla/dom/WindowGlobalChild.h" 23 #include "nsComboboxControlFrame.h" 24 #include "nsContentCreatorFunctions.h" 25 #include "nsContentList.h" 26 #include "nsContentUtils.h" 27 #include "nsError.h" 28 #include "nsGkAtoms.h" 29 #include "nsIFrame.h" 30 #include "nsISelectControlFrame.h" 31 #include "nsLayoutUtils.h" 32 #include "nsListControlFrame.h" 33 #include "nsServiceManagerUtils.h" 34 #include "nsStyleConsts.h" 35 #include "nsTextNode.h" 36 37 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Select) 38 39 namespace mozilla::dom { 40 41 //---------------------------------------------------------------------- 42 // 43 // SafeOptionListMutation 44 // 45 46 SafeOptionListMutation::SafeOptionListMutation(nsIContent* aSelect, 47 nsIContent* aParent, 48 nsIContent* aKid, 49 uint32_t aIndex, bool aNotify) 50 : mSelect(HTMLSelectElement::FromNodeOrNull(aSelect)), 51 mTopLevelMutation(false), 52 mNeedsRebuild(false), 53 mNotify(aNotify) { 54 if (mSelect) { 55 mInitialSelectedOption = mSelect->Item(mSelect->SelectedIndex()); 56 mTopLevelMutation = !mSelect->mMutating; 57 if (mTopLevelMutation) { 58 mSelect->mMutating = true; 59 } else { 60 // This is very unfortunate, but to handle mutation events properly, 61 // option list must be up-to-date before inserting or removing options. 62 // Fortunately this is called only if mutation event listener 63 // adds or removes options. 64 mSelect->RebuildOptionsArray(mNotify); 65 } 66 nsresult rv; 67 if (aKid) { 68 rv = mSelect->WillAddOptions(aKid, aParent, aIndex, mNotify); 69 } else { 70 rv = mSelect->WillRemoveOptions(aParent, aIndex, mNotify); 71 } 72 mNeedsRebuild = NS_FAILED(rv); 73 } 74 } 75 76 SafeOptionListMutation::~SafeOptionListMutation() { 77 if (mSelect) { 78 if (mNeedsRebuild || (mTopLevelMutation && mGuard.Mutated(1))) { 79 mSelect->RebuildOptionsArray(true); 80 } 81 if (mTopLevelMutation) { 82 mSelect->mMutating = false; 83 } 84 if (mSelect->Item(mSelect->SelectedIndex()) != mInitialSelectedOption) { 85 // We must have triggered the SelectSomething() codepath, which can cause 86 // our validity to change. Unfortunately, our attempt to update validity 87 // in that case may not have worked correctly, because we actually call it 88 // before we have inserted the new <option>s into the DOM! Go ahead and 89 // update validity here as needed, because by now we know our <option>s 90 // are where they should be. 91 mSelect->UpdateValueMissingValidityState(); 92 mSelect->UpdateValidityElementStates(mNotify); 93 } 94 #ifdef DEBUG 95 mSelect->VerifyOptionsArray(); 96 #endif 97 } 98 } 99 100 //---------------------------------------------------------------------- 101 // 102 // HTMLSelectElement 103 // 104 105 // construction, destruction 106 107 HTMLSelectElement::HTMLSelectElement( 108 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, 109 FromParser aFromParser) 110 : nsGenericHTMLFormControlElementWithState( 111 std::move(aNodeInfo), aFromParser, FormControlType::Select), 112 mOptions(new HTMLOptionsCollection(this)), 113 mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown), 114 mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown), 115 mIsDoneAddingChildren(!aFromParser), 116 mDisabledChanged(false), 117 mMutating(false), 118 mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)), 119 mUserInteracted(false), 120 mDefaultSelectionSet(false), 121 mIsOpenInParentProcess(false), 122 mNonOptionChildren(0), 123 mOptGroupCount(0), 124 mSelectedIndex(-1) { 125 SetHasWeirdParserInsertionMode(); 126 127 // DoneAddingChildren() will be called later if it's from the parser, 128 // otherwise it is 129 130 // Set up our default state: enabled, optional, and valid. 131 AddStatesSilently(ElementState::ENABLED | ElementState::OPTIONAL_ | 132 ElementState::VALID); 133 } 134 135 // ISupports 136 137 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLSelectElement) 138 139 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED( 140 HTMLSelectElement, nsGenericHTMLFormControlElementWithState) 141 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity) 142 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOptions) 143 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedOptions) 144 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 145 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED( 146 HTMLSelectElement, nsGenericHTMLFormControlElementWithState) 147 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity) 148 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedOptions) 149 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 150 151 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED( 152 HTMLSelectElement, nsGenericHTMLFormControlElementWithState, 153 nsIConstraintValidation) 154 155 // nsIDOMHTMLSelectElement 156 157 NS_IMPL_ELEMENT_CLONE(HTMLSelectElement) 158 159 void HTMLSelectElement::SetCustomValidity(const nsAString& aError) { 160 ConstraintValidation::SetCustomValidity(aError); 161 UpdateValidityElementStates(true); 162 } 163 164 // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker 165 void HTMLSelectElement::ShowPicker(ErrorResult& aRv) { 166 // Step 1. If this is not mutable, then throw an "InvalidStateError" 167 // DOMException. 168 if (IsDisabled()) { 169 return aRv.ThrowInvalidStateError("This select is disabled."); 170 } 171 172 // Step 2. If this's relevant settings object's origin is not same origin with 173 // this's relevant settings object's top-level origin, and this is a select 174 // element, [...], then throw a "SecurityError" DOMException. 175 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow(); 176 WindowGlobalChild* windowGlobalChild = 177 window ? window->GetWindowGlobalChild() : nullptr; 178 if (!windowGlobalChild || !windowGlobalChild->SameOriginWithTop()) { 179 return aRv.ThrowSecurityError( 180 "Call was blocked because the current origin isn't same-origin with " 181 "top."); 182 } 183 184 // Step 3. If this's relevant global object does not have transient 185 // activation, then throw a "NotAllowedError" DOMException. 186 if (!OwnerDoc()->HasValidTransientUserGestureActivation()) { 187 return aRv.ThrowNotAllowedError( 188 "Call was blocked due to lack of user activation."); 189 } 190 191 // Step 4. If this is a select element, and this is not being rendered, then 192 // throw a "NotSupportedError" DOMException. 193 194 // Flush frames so that IsRendered returns up-to-date results. 195 (void)GetPrimaryFrame(FlushType::Frames); 196 if (!IsRendered()) { 197 return aRv.ThrowNotSupportedError("This select isn't being rendered."); 198 } 199 200 // Step 5. Show the picker, if applicable, for this. 201 // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable 202 // To show the picker, if applicable for an input element element: 203 // We already checked if mutable and user activation earlier, so skip 1 & 2. 204 205 // Step 3. Consume user activation given element's relevant global object. 206 OwnerDoc()->ConsumeTransientUserGestureActivation(); 207 208 // Step 5. Otherwise, the user agent should show any relevant user interface 209 // for selecting a value for element, in the way it normally would when the 210 // user interacts with the control. 211 #if !defined(ANDROID) 212 if (!IsCombobox()) { 213 return; 214 } 215 #endif 216 217 if (!IsInActiveTab(OwnerDoc())) { 218 return; 219 } 220 221 if (!OpenInParentProcess()) { 222 RefPtr<Document> doc = OwnerDoc(); 223 nsContentUtils::DispatchChromeEvent(doc, this, u"mozshowdropdown"_ns, 224 CanBubble::eYes, Cancelable::eNo); 225 } 226 } 227 228 void HTMLSelectElement::GetAutocomplete(DOMString& aValue) { 229 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete); 230 231 mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute( 232 attributeVal, aValue, mAutocompleteAttrState); 233 } 234 235 void HTMLSelectElement::GetAutocompleteInfo(AutocompleteInfo& aInfo) { 236 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete); 237 mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute( 238 attributeVal, aInfo, mAutocompleteInfoState, true); 239 } 240 241 void HTMLSelectElement::InsertChildBefore( 242 nsIContent* aKid, nsIContent* aBeforeThis, bool aNotify, ErrorResult& aRv, 243 nsINode* aOldParent, MutationEffectOnScript aMutationEffectOnScript) { 244 const uint32_t index = 245 aBeforeThis ? *ComputeIndexOf(aBeforeThis) : GetChildCount(); 246 SafeOptionListMutation safeMutation(this, this, aKid, index, aNotify); 247 nsGenericHTMLFormControlElementWithState::InsertChildBefore( 248 aKid, aBeforeThis, aNotify, aRv, aOldParent, aMutationEffectOnScript); 249 if (aRv.Failed()) { 250 safeMutation.MutationFailed(); 251 } 252 } 253 254 void HTMLSelectElement::RemoveChildNode( 255 nsIContent* aKid, bool aNotify, const BatchRemovalState* aState, 256 nsINode* aNewParent, MutationEffectOnScript aMutationEffectOnScript) { 257 SafeOptionListMutation safeMutation(this, this, nullptr, 258 *ComputeIndexOf(aKid), aNotify); 259 nsGenericHTMLFormControlElementWithState::RemoveChildNode( 260 aKid, aNotify, aState, aNewParent, aMutationEffectOnScript); 261 } 262 263 void HTMLSelectElement::InsertOptionsIntoList(nsIContent* aOptions, 264 int32_t aListIndex, 265 int32_t aDepth, bool aNotify) { 266 MOZ_ASSERT(aDepth == 0 || aDepth == 1); 267 int32_t insertIndex = aListIndex; 268 269 HTMLOptionElement* optElement = HTMLOptionElement::FromNode(aOptions); 270 if (optElement) { 271 mOptions->InsertOptionAt(optElement, insertIndex); 272 insertIndex++; 273 } else if (aDepth == 0) { 274 // If it's at the top level, then we just found out there are non-options 275 // at the top level, which will throw off the insert count 276 mNonOptionChildren++; 277 278 // Deal with optgroups 279 if (aOptions->IsHTMLElement(nsGkAtoms::optgroup)) { 280 mOptGroupCount++; 281 282 for (nsIContent* child = aOptions->GetFirstChild(); child; 283 child = child->GetNextSibling()) { 284 optElement = HTMLOptionElement::FromNode(child); 285 if (optElement) { 286 mOptions->InsertOptionAt(optElement, insertIndex); 287 insertIndex++; 288 } 289 } 290 } 291 } // else ignore even if optgroup; we want to ignore nested optgroups. 292 293 // Deal with the selected list 294 if (insertIndex - aListIndex) { 295 // Fix the currently selected index 296 if (aListIndex <= mSelectedIndex) { 297 mSelectedIndex += (insertIndex - aListIndex); 298 OnSelectionChanged(); 299 } 300 301 // Get the frame stuff for notification. No need to flush here 302 // since if there's no frame for the select yet the select will 303 // get into the right state once it's created. 304 nsISelectControlFrame* selectFrame = nullptr; 305 AutoWeakFrame weakSelectFrame; 306 bool didGetFrame = false; 307 308 // Actually select the options if the added options warrant it 309 for (int32_t i = aListIndex; i < insertIndex; i++) { 310 // Notify the frame that the option is added 311 if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) { 312 selectFrame = GetSelectFrame(); 313 weakSelectFrame = do_QueryFrame(selectFrame); 314 didGetFrame = true; 315 } 316 317 if (selectFrame) { 318 selectFrame->AddOption(i); 319 } 320 321 RefPtr<HTMLOptionElement> option = Item(i); 322 if (option && option->Selected()) { 323 // Clear all other options 324 if (!HasAttr(nsGkAtoms::multiple)) { 325 OptionFlags mask{OptionFlag::IsSelected, OptionFlag::ClearAll, 326 OptionFlag::SetDisabled, OptionFlag::Notify, 327 OptionFlag::InsertingOptions}; 328 SetOptionsSelectedByIndex(i, i, mask); 329 } 330 331 // This is sort of a hack ... we need to notify that the option was 332 // set and change selectedIndex even though we didn't really change 333 // its value. 334 OnOptionSelected(selectFrame, i, true, false, aNotify); 335 } 336 } 337 338 CheckSelectSomething(aNotify); 339 } 340 } 341 342 nsresult HTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions, 343 int32_t aListIndex, 344 int32_t aDepth, 345 bool aNotify) { 346 MOZ_ASSERT(aDepth == 0 || aDepth == 1); 347 int32_t numRemoved = 0; 348 349 HTMLOptionElement* optElement = HTMLOptionElement::FromNode(aOptions); 350 if (optElement) { 351 if (mOptions->ItemAsOption(aListIndex) != optElement) { 352 NS_ERROR("wrong option at index"); 353 return NS_ERROR_UNEXPECTED; 354 } 355 mOptions->RemoveOptionAt(aListIndex); 356 numRemoved++; 357 } else if (aDepth == 0) { 358 // Yay, one less artifact at the top level. 359 mNonOptionChildren--; 360 361 // Recurse down deeper for options 362 if (mOptGroupCount && aOptions->IsHTMLElement(nsGkAtoms::optgroup)) { 363 mOptGroupCount--; 364 365 for (nsIContent* child = aOptions->GetFirstChild(); child; 366 child = child->GetNextSibling()) { 367 optElement = HTMLOptionElement::FromNode(child); 368 if (optElement) { 369 if (mOptions->ItemAsOption(aListIndex) != optElement) { 370 NS_ERROR("wrong option at index"); 371 return NS_ERROR_UNEXPECTED; 372 } 373 mOptions->RemoveOptionAt(aListIndex); 374 numRemoved++; 375 } 376 } 377 } 378 } // else don't check for an optgroup; we want to ignore nested optgroups 379 380 if (numRemoved) { 381 // Tell the widget we removed the options 382 nsISelectControlFrame* selectFrame = GetSelectFrame(); 383 if (selectFrame) { 384 nsAutoScriptBlocker scriptBlocker; 385 for (int32_t i = aListIndex; i < aListIndex + numRemoved; ++i) { 386 selectFrame->RemoveOption(i); 387 } 388 } 389 390 // Fix the selected index 391 if (aListIndex <= mSelectedIndex) { 392 if (mSelectedIndex < (aListIndex + numRemoved)) { 393 // aListIndex <= mSelectedIndex < aListIndex+numRemoved 394 // Find a new selected index if it was one of the ones removed. 395 // If this is a Combobox, no other Item will be selected. 396 if (IsCombobox()) { 397 mSelectedIndex = -1; 398 OnSelectionChanged(); 399 } else { 400 FindSelectedIndex(aListIndex, aNotify); 401 } 402 } else { 403 // Shift the selected index if something in front of it was removed 404 // aListIndex+numRemoved <= mSelectedIndex 405 mSelectedIndex -= numRemoved; 406 OnSelectionChanged(); 407 } 408 } 409 410 // Select something in case we removed the selected option on a 411 // single select 412 if (!CheckSelectSomething(aNotify) && mSelectedIndex == -1) { 413 // Update the validity state in case of we've just removed the last 414 // option. 415 UpdateValueMissingValidityState(); 416 UpdateValidityElementStates(aNotify); 417 } 418 } 419 420 return NS_OK; 421 } 422 423 // XXXldb Doing the processing before the content nodes have been added 424 // to the document (as the name of this function seems to require, and 425 // as the callers do), is highly unusual. Passing around unparented 426 // content to other parts of the app can make those things think the 427 // options are the root content node. 428 NS_IMETHODIMP 429 HTMLSelectElement::WillAddOptions(nsIContent* aOptions, nsIContent* aParent, 430 int32_t aContentIndex, bool aNotify) { 431 if (this != aParent && this != aParent->GetParent()) { 432 return NS_OK; 433 } 434 int32_t level = aParent == this ? 0 : 1; 435 436 // Get the index where the options will be inserted 437 int32_t ind = -1; 438 if (!mNonOptionChildren) { 439 // If there are no artifacts, aContentIndex == ind 440 ind = aContentIndex; 441 } else { 442 // If there are artifacts, we have to get the index of the option the 443 // hard way 444 int32_t children = aParent->GetChildCount(); 445 446 if (aContentIndex >= children) { 447 // If the content insert is after the end of the parent, then we want to 448 // get the next index *after* the parent and insert there. 449 ind = GetOptionIndexAfter(aParent); 450 } else { 451 // If the content insert is somewhere in the middle of the container, then 452 // we want to get the option currently at the index and insert in front of 453 // that. 454 nsIContent* currentKid = aParent->GetChildAt_Deprecated(aContentIndex); 455 NS_ASSERTION(currentKid, "Child not found!"); 456 if (currentKid) { 457 ind = GetOptionIndexAt(currentKid); 458 } else { 459 ind = -1; 460 } 461 } 462 } 463 464 InsertOptionsIntoList(aOptions, ind, level, aNotify); 465 return NS_OK; 466 } 467 468 NS_IMETHODIMP 469 HTMLSelectElement::WillRemoveOptions(nsIContent* aParent, int32_t aContentIndex, 470 bool aNotify) { 471 if (this != aParent && this != aParent->GetParent()) { 472 return NS_OK; 473 } 474 int32_t level = this == aParent ? 0 : 1; 475 476 // Get the index where the options will be removed 477 nsIContent* currentKid = aParent->GetChildAt_Deprecated(aContentIndex); 478 if (currentKid) { 479 int32_t ind; 480 if (!mNonOptionChildren) { 481 // If there are no artifacts, aContentIndex == ind 482 ind = aContentIndex; 483 } else { 484 // If there are artifacts, we have to get the index of the option the 485 // hard way 486 ind = GetFirstOptionIndex(currentKid); 487 } 488 if (ind != -1) { 489 nsresult rv = RemoveOptionsFromList(currentKid, ind, level, aNotify); 490 NS_ENSURE_SUCCESS(rv, rv); 491 } 492 } 493 494 return NS_OK; 495 } 496 497 int32_t HTMLSelectElement::GetOptionIndexAt(nsIContent* aOptions) { 498 // Search this node and below. 499 // If not found, find the first one *after* this node. 500 int32_t retval = GetFirstOptionIndex(aOptions); 501 if (retval == -1) { 502 retval = GetOptionIndexAfter(aOptions); 503 } 504 505 return retval; 506 } 507 508 int32_t HTMLSelectElement::GetOptionIndexAfter(nsIContent* aOptions) { 509 // - If this is the select, the next option is the last. 510 // - If not, search all the options after aOptions and up to the last option 511 // in the parent. 512 // - If it's not there, search for the first option after the parent. 513 if (aOptions == this) { 514 return Length(); 515 } 516 517 int32_t retval = -1; 518 519 nsCOMPtr<nsIContent> parent = aOptions->GetParent(); 520 521 if (parent) { 522 const int32_t index = parent->ComputeIndexOf_Deprecated(aOptions); 523 const int32_t count = static_cast<int32_t>(parent->GetChildCount()); 524 525 retval = GetFirstChildOptionIndex(parent, index + 1, count); 526 527 if (retval == -1) { 528 retval = GetOptionIndexAfter(parent); 529 } 530 } 531 532 return retval; 533 } 534 535 int32_t HTMLSelectElement::GetFirstOptionIndex(nsIContent* aOptions) { 536 int32_t listIndex = -1; 537 HTMLOptionElement* optElement = HTMLOptionElement::FromNode(aOptions); 538 if (optElement) { 539 mOptions->GetOptionIndex(optElement, 0, true, &listIndex); 540 return listIndex; 541 } 542 543 listIndex = GetFirstChildOptionIndex(aOptions, 0, aOptions->GetChildCount()); 544 545 return listIndex; 546 } 547 548 int32_t HTMLSelectElement::GetFirstChildOptionIndex(nsIContent* aOptions, 549 int32_t aStartIndex, 550 int32_t aEndIndex) { 551 int32_t retval = -1; 552 553 for (int32_t i = aStartIndex; i < aEndIndex; ++i) { 554 retval = GetFirstOptionIndex(aOptions->GetChildAt_Deprecated(i)); 555 if (retval != -1) { 556 break; 557 } 558 } 559 560 return retval; 561 } 562 563 nsISelectControlFrame* HTMLSelectElement::GetSelectFrame() { 564 return do_QueryFrame(GetPrimaryFrame()); 565 } 566 567 void HTMLSelectElement::Add( 568 const HTMLOptionElementOrHTMLOptGroupElement& aElement, 569 const Nullable<HTMLElementOrLong>& aBefore, ErrorResult& aRv) { 570 nsGenericHTMLElement& element = 571 aElement.IsHTMLOptionElement() ? static_cast<nsGenericHTMLElement&>( 572 aElement.GetAsHTMLOptionElement()) 573 : static_cast<nsGenericHTMLElement&>( 574 aElement.GetAsHTMLOptGroupElement()); 575 576 if (aBefore.IsNull()) { 577 Add(element, static_cast<nsGenericHTMLElement*>(nullptr), aRv); 578 } else if (aBefore.Value().IsHTMLElement()) { 579 Add(element, &aBefore.Value().GetAsHTMLElement(), aRv); 580 } else { 581 Add(element, aBefore.Value().GetAsLong(), aRv); 582 } 583 } 584 585 void HTMLSelectElement::Add(nsGenericHTMLElement& aElement, 586 nsGenericHTMLElement* aBefore, 587 ErrorResult& aError) { 588 if (!aBefore) { 589 Element::AppendChild(aElement, aError); 590 return; 591 } 592 593 // Just in case we're not the parent, get the parent of the reference 594 // element 595 nsCOMPtr<nsINode> parent = aBefore->Element::GetParentNode(); 596 if (!parent || !parent->IsInclusiveDescendantOf(this)) { 597 // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT 598 // element. 599 aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); 600 return; 601 } 602 603 // If the before parameter is not null, we are equivalent to the 604 // insertBefore method on the parent of before. 605 nsCOMPtr<nsINode> refNode = aBefore; 606 parent->InsertBefore(aElement, refNode, aError); 607 } 608 609 void HTMLSelectElement::Remove(int32_t aIndex) const { 610 if (aIndex < 0) { 611 return; 612 } 613 614 nsCOMPtr<nsINode> option = Item(static_cast<uint32_t>(aIndex)); 615 if (!option) { 616 return; 617 } 618 619 option->Remove(); 620 } 621 622 void HTMLSelectElement::GetType(nsAString& aType) { 623 if (HasAttr(nsGkAtoms::multiple)) { 624 aType.AssignLiteral("select-multiple"); 625 } else { 626 aType.AssignLiteral("select-one"); 627 } 628 } 629 630 void HTMLSelectElement::SetLength(uint32_t aLength, ErrorResult& aRv) { 631 constexpr uint32_t kMaxDynamicSelectLength = 100000; 632 633 uint32_t curlen = Length(); 634 635 if (curlen > aLength) { // Remove extra options 636 for (uint32_t i = curlen; i > aLength; --i) { 637 Remove(i - 1); 638 } 639 } else if (aLength > curlen) { 640 if (aLength > kMaxDynamicSelectLength) { 641 nsAutoString strOptionsLength; 642 strOptionsLength.AppendInt(aLength); 643 644 nsAutoString strLimit; 645 strLimit.AppendInt(kMaxDynamicSelectLength); 646 647 nsContentUtils::ReportToConsole( 648 nsIScriptError::warningFlag, "DOM"_ns, OwnerDoc(), 649 nsContentUtils::eDOM_PROPERTIES, 650 "SelectOptionsLengthAssignmentWarning", {strOptionsLength, strLimit}); 651 return; 652 } 653 654 RefPtr<mozilla::dom::NodeInfo> nodeInfo; 655 656 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::option, 657 getter_AddRefs(nodeInfo)); 658 659 nsCOMPtr<nsINode> node = NS_NewHTMLOptionElement(nodeInfo.forget()); 660 for (uint32_t i = curlen; i < aLength; i++) { 661 nsINode::AppendChild(*node, aRv); 662 if (aRv.Failed()) { 663 return; 664 } 665 666 if (i + 1 < aLength) { 667 node = node->CloneNode(true, aRv); 668 if (aRv.Failed()) { 669 return; 670 } 671 MOZ_ASSERT(node); 672 } 673 } 674 } 675 } 676 677 /* static */ 678 bool HTMLSelectElement::MatchSelectedOptions(Element* aElement, 679 int32_t /* unused */, 680 nsAtom* /* unused */, 681 void* /* unused*/) { 682 HTMLOptionElement* option = HTMLOptionElement::FromNode(aElement); 683 return option && option->Selected(); 684 } 685 686 nsIHTMLCollection* HTMLSelectElement::SelectedOptions() { 687 if (!mSelectedOptions) { 688 mSelectedOptions = new nsContentList(this, MatchSelectedOptions, nullptr, 689 nullptr, /* deep */ true); 690 } 691 return mSelectedOptions; 692 } 693 694 void HTMLSelectElement::SetSelectedIndexInternal(int32_t aIndex, bool aNotify) { 695 int32_t oldSelectedIndex = mSelectedIndex; 696 OptionFlags mask{OptionFlag::IsSelected, OptionFlag::ClearAll, 697 OptionFlag::SetDisabled}; 698 if (aNotify) { 699 mask += OptionFlag::Notify; 700 } 701 702 SetOptionsSelectedByIndex(aIndex, aIndex, mask); 703 704 nsISelectControlFrame* selectFrame = GetSelectFrame(); 705 if (selectFrame) { 706 selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex); 707 } 708 709 OnSelectionChanged(); 710 } 711 712 bool HTMLSelectElement::IsOptionSelectedByIndex(int32_t aIndex) const { 713 HTMLOptionElement* option = Item(static_cast<uint32_t>(aIndex)); 714 return option && option->Selected(); 715 } 716 717 void HTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame, 718 int32_t aIndex, bool aSelected, 719 bool aChangeOptionState, 720 bool aNotify) { 721 // Set the selected index 722 if (aSelected && (aIndex < mSelectedIndex || mSelectedIndex < 0)) { 723 mSelectedIndex = aIndex; 724 OnSelectionChanged(); 725 } else if (!aSelected && aIndex == mSelectedIndex) { 726 FindSelectedIndex(aIndex + 1, aNotify); 727 } 728 729 if (aChangeOptionState) { 730 // Tell the option to get its bad self selected 731 RefPtr<HTMLOptionElement> option = Item(static_cast<uint32_t>(aIndex)); 732 if (option) { 733 option->SetSelectedInternal(aSelected, aNotify); 734 } 735 } 736 737 // Let the frame know too 738 if (aSelectFrame) { 739 aSelectFrame->OnOptionSelected(aIndex, aSelected); 740 } 741 742 UpdateSelectedOptions(); 743 UpdateValueMissingValidityState(); 744 UpdateValidityElementStates(aNotify); 745 } 746 747 void HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex, bool aNotify) { 748 mSelectedIndex = -1; 749 uint32_t len = Length(); 750 for (int32_t i = aStartIndex; i < int32_t(len); i++) { 751 if (IsOptionSelectedByIndex(i)) { 752 mSelectedIndex = i; 753 break; 754 } 755 } 756 OnSelectionChanged(); 757 } 758 759 // XXX Consider splitting this into two functions for ease of reading: 760 // SelectOptionsByIndex(startIndex, endIndex, clearAll, checkDisabled) 761 // startIndex, endIndex - the range of options to turn on 762 // (-1, -1) will clear all indices no matter what. 763 // clearAll - will clear all other options unless checkDisabled is on 764 // and all the options attempted to be set are disabled 765 // (note that if it is not multiple, and an option is selected, 766 // everything else will be cleared regardless). 767 // checkDisabled - if this is TRUE, and an option is disabled, it will not be 768 // changed regardless of whether it is selected or not. 769 // Generally the UI passes TRUE and JS passes FALSE. 770 // (setDisabled currently is the opposite) 771 // DeselectOptionsByIndex(startIndex, endIndex, checkDisabled) 772 // startIndex, endIndex - the range of options to turn on 773 // (-1, -1) will clear all indices no matter what. 774 // checkDisabled - if this is TRUE, and an option is disabled, it will not be 775 // changed regardless of whether it is selected or not. 776 // Generally the UI passes TRUE and JS passes FALSE. 777 // (setDisabled currently is the opposite) 778 // 779 // XXXbz the above comment is pretty confusing. Maybe we should actually 780 // document the args to this function too, in addition to documenting what 781 // things might end up looking like? In particular, pay attention to the 782 // setDisabled vs checkDisabled business. 783 bool HTMLSelectElement::SetOptionsSelectedByIndex(int32_t aStartIndex, 784 int32_t aEndIndex, 785 OptionFlags aOptionsMask) { 786 #if 0 787 printf("SetOption(%d-%d, %c, ClearAll=%c)\n", aStartIndex, aEndIndex, 788 (aOptionsMask.contains(OptionFlag::IsSelected) ? 'Y' : 'N'), 789 (aOptionsMask.contains(OptionFlag::ClearAll) ? 'Y' : 'N')); 790 #endif 791 // Don't bother if the select is disabled 792 if (!aOptionsMask.contains(OptionFlag::SetDisabled) && IsDisabled()) { 793 return false; 794 } 795 796 // Don't bother if there are no options 797 uint32_t numItems = Length(); 798 if (numItems == 0) { 799 return false; 800 } 801 802 // First, find out whether multiple items can be selected 803 bool isMultiple = Multiple(); 804 805 // These variables tell us whether any options were selected 806 // or deselected. 807 bool optionsSelected = false; 808 bool optionsDeselected = false; 809 810 nsISelectControlFrame* selectFrame = nullptr; 811 bool didGetFrame = false; 812 AutoWeakFrame weakSelectFrame; 813 814 if (aOptionsMask.contains(OptionFlag::IsSelected)) { 815 // Setting selectedIndex to an out-of-bounds index means -1. (HTML5) 816 if (aStartIndex < 0 || AssertedCast<uint32_t>(aStartIndex) >= numItems || 817 aEndIndex < 0 || AssertedCast<uint32_t>(aEndIndex) >= numItems) { 818 aStartIndex = -1; 819 aEndIndex = -1; 820 } 821 822 // Only select the first value if it's not multiple 823 if (!isMultiple) { 824 aEndIndex = aStartIndex; 825 } 826 827 // This variable tells whether or not all of the options we attempted to 828 // select are disabled. If ClearAll is passed in as true, and we do not 829 // select anything because the options are disabled, we will not clear the 830 // other options. (This is to make the UI work the way one might expect.) 831 bool allDisabled = !aOptionsMask.contains(OptionFlag::SetDisabled); 832 833 // 834 // Save a little time when clearing other options 835 // 836 int32_t previousSelectedIndex = mSelectedIndex; 837 838 // 839 // Select the requested indices 840 // 841 // If index is -1, everything will be deselected (bug 28143) 842 if (aStartIndex != -1) { 843 MOZ_ASSERT(aStartIndex >= 0); 844 MOZ_ASSERT(aEndIndex >= 0); 845 // Loop through the options and select them (if they are not disabled and 846 // if they are not already selected). 847 for (uint32_t optIndex = AssertedCast<uint32_t>(aStartIndex); 848 optIndex <= AssertedCast<uint32_t>(aEndIndex); optIndex++) { 849 RefPtr<HTMLOptionElement> option = Item(optIndex); 850 851 // Ignore disabled options. 852 if (!aOptionsMask.contains(OptionFlag::SetDisabled)) { 853 if (option && IsOptionDisabled(option)) { 854 continue; 855 } 856 allDisabled = false; 857 } 858 859 // If the index is already selected, ignore it. On the other hand when 860 // the option has just been inserted we have to get in sync with it. 861 if (option && (aOptionsMask.contains(OptionFlag::InsertingOptions) || 862 !option->Selected())) { 863 // To notify the frame if anything gets changed. No need 864 // to flush here, if there's no frame yet we don't need to 865 // force it to be created just to notify it about a change 866 // in the select. 867 selectFrame = GetSelectFrame(); 868 weakSelectFrame = do_QueryFrame(selectFrame); 869 didGetFrame = true; 870 871 OnOptionSelected(selectFrame, optIndex, true, !option->Selected(), 872 aOptionsMask.contains(OptionFlag::Notify)); 873 optionsSelected = true; 874 } 875 } 876 } 877 878 // Next remove all other options if single select or all is clear 879 // If index is -1, everything will be deselected (bug 28143) 880 if (((!isMultiple && optionsSelected) || 881 (aOptionsMask.contains(OptionFlag::ClearAll) && !allDisabled) || 882 aStartIndex == -1) && 883 previousSelectedIndex != -1) { 884 for (uint32_t optIndex = AssertedCast<uint32_t>(previousSelectedIndex); 885 optIndex < numItems; optIndex++) { 886 if (static_cast<int32_t>(optIndex) < aStartIndex || 887 static_cast<int32_t>(optIndex) > aEndIndex) { 888 HTMLOptionElement* option = Item(optIndex); 889 // If the index is already deselected, ignore it. 890 if (option && option->Selected()) { 891 if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) { 892 // To notify the frame if anything gets changed, don't 893 // flush, if the frame doesn't exist we don't need to 894 // create it just to tell it about this change. 895 selectFrame = GetSelectFrame(); 896 weakSelectFrame = do_QueryFrame(selectFrame); 897 898 didGetFrame = true; 899 } 900 901 OnOptionSelected(selectFrame, optIndex, false, true, 902 aOptionsMask.contains(OptionFlag::Notify)); 903 optionsDeselected = true; 904 905 // Only need to deselect one option if not multiple 906 if (!isMultiple) { 907 break; 908 } 909 } 910 } 911 } 912 } 913 } else { 914 // If we're deselecting, loop through all selected items and deselect 915 // any that are in the specified range. 916 for (int32_t optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) { 917 HTMLOptionElement* option = Item(optIndex); 918 if (!aOptionsMask.contains(OptionFlag::SetDisabled) && 919 IsOptionDisabled(option)) { 920 continue; 921 } 922 923 // If the index is already selected, ignore it. 924 if (option && option->Selected()) { 925 if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) { 926 // To notify the frame if anything gets changed, don't 927 // flush, if the frame doesn't exist we don't need to 928 // create it just to tell it about this change. 929 selectFrame = GetSelectFrame(); 930 weakSelectFrame = do_QueryFrame(selectFrame); 931 932 didGetFrame = true; 933 } 934 935 OnOptionSelected(selectFrame, optIndex, false, true, 936 aOptionsMask.contains(OptionFlag::Notify)); 937 optionsDeselected = true; 938 } 939 } 940 } 941 942 // Make sure something is selected unless we were set to -1 (none) 943 if (optionsDeselected && aStartIndex != -1 && 944 !aOptionsMask.contains(OptionFlag::NoReselect)) { 945 optionsSelected = 946 CheckSelectSomething(aOptionsMask.contains(OptionFlag::Notify)) || 947 optionsSelected; 948 } 949 950 // Let the caller know whether anything was changed 951 return optionsSelected || optionsDeselected; 952 } 953 954 NS_IMETHODIMP 955 HTMLSelectElement::IsOptionDisabled(int32_t aIndex, bool* aIsDisabled) { 956 *aIsDisabled = false; 957 RefPtr<HTMLOptionElement> option = Item(aIndex); 958 NS_ENSURE_TRUE(option, NS_ERROR_FAILURE); 959 960 *aIsDisabled = IsOptionDisabled(option); 961 return NS_OK; 962 } 963 964 bool HTMLSelectElement::IsOptionDisabled(HTMLOptionElement* aOption) const { 965 MOZ_ASSERT(aOption); 966 if (aOption->Disabled()) { 967 return true; 968 } 969 970 // Check for disabled optgroups 971 // If there are no artifacts, there are no optgroups 972 if (mNonOptionChildren) { 973 for (nsCOMPtr<Element> node = 974 static_cast<nsINode*>(aOption)->GetParentElement(); 975 node; node = node->GetParentElement()) { 976 // If we reached the select element, we're done 977 if (node->IsHTMLElement(nsGkAtoms::select)) { 978 return false; 979 } 980 981 RefPtr<HTMLOptGroupElement> optGroupElement = 982 HTMLOptGroupElement::FromNode(node); 983 984 if (!optGroupElement) { 985 // If you put something else between you and the optgroup, you're a 986 // moron and you deserve not to have optgroup disabling work. 987 return false; 988 } 989 990 if (optGroupElement->Disabled()) { 991 return true; 992 } 993 } 994 } 995 996 return false; 997 } 998 999 void HTMLSelectElement::GetValue(DOMString& aValue) const { 1000 int32_t selectedIndex = SelectedIndex(); 1001 if (selectedIndex < 0) { 1002 return; 1003 } 1004 1005 RefPtr<HTMLOptionElement> option = Item(static_cast<uint32_t>(selectedIndex)); 1006 1007 if (!option) { 1008 return; 1009 } 1010 1011 option->GetValue(aValue); 1012 } 1013 1014 void HTMLSelectElement::SetValue(const nsAString& aValue) { 1015 uint32_t length = Length(); 1016 1017 for (uint32_t i = 0; i < length; i++) { 1018 RefPtr<HTMLOptionElement> option = Item(i); 1019 if (!option) { 1020 continue; 1021 } 1022 1023 nsAutoString optionVal; 1024 option->GetValue(optionVal); 1025 if (optionVal.Equals(aValue)) { 1026 SetSelectedIndexInternal(int32_t(i), true); 1027 return; 1028 } 1029 } 1030 // No matching option was found. 1031 SetSelectedIndexInternal(-1, true); 1032 } 1033 1034 int32_t HTMLSelectElement::TabIndexDefault() { return 0; } 1035 1036 bool HTMLSelectElement::IsHTMLFocusable(IsFocusableFlags aFlags, 1037 bool* aIsFocusable, 1038 int32_t* aTabIndex) { 1039 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable( 1040 aFlags, aIsFocusable, aTabIndex)) { 1041 return true; 1042 } 1043 1044 *aIsFocusable = !IsDisabled(); 1045 1046 return false; 1047 } 1048 1049 bool HTMLSelectElement::CheckSelectSomething(bool aNotify) { 1050 if (mIsDoneAddingChildren) { 1051 if (mSelectedIndex < 0 && IsCombobox()) { 1052 return SelectSomething(aNotify); 1053 } 1054 } 1055 return false; 1056 } 1057 1058 bool HTMLSelectElement::SelectSomething(bool aNotify) { 1059 // If we're not done building the select, don't play with this yet. 1060 if (!mIsDoneAddingChildren) { 1061 return false; 1062 } 1063 1064 uint32_t count = Length(); 1065 for (uint32_t i = 0; i < count; i++) { 1066 bool disabled; 1067 nsresult rv = IsOptionDisabled(i, &disabled); 1068 1069 if (NS_FAILED(rv) || !disabled) { 1070 SetSelectedIndexInternal(i, aNotify); 1071 1072 UpdateValueMissingValidityState(); 1073 UpdateValidityElementStates(aNotify); 1074 1075 return true; 1076 } 1077 } 1078 1079 return false; 1080 } 1081 1082 nsresult HTMLSelectElement::BindToTree(BindContext& aContext, 1083 nsINode& aParent) { 1084 nsresult rv = 1085 nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent); 1086 NS_ENSURE_SUCCESS(rv, rv); 1087 1088 // If there is a disabled fieldset in the parent chain, the element is now 1089 // barred from constraint validation. 1090 // XXXbz is this still needed now that fieldset changes always call 1091 // FieldSetDisabledChanged? 1092 UpdateBarredFromConstraintValidation(); 1093 1094 // And now make sure our state is up to date 1095 UpdateValidityElementStates(false); 1096 1097 return rv; 1098 } 1099 1100 void HTMLSelectElement::UnbindFromTree(UnbindContext& aContext) { 1101 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext); 1102 1103 // We might be no longer disabled because our parent chain changed. 1104 // XXXbz is this still needed now that fieldset changes always call 1105 // FieldSetDisabledChanged? 1106 UpdateBarredFromConstraintValidation(); 1107 1108 // And now make sure our state is up to date 1109 UpdateValidityElementStates(false); 1110 } 1111 1112 void HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, 1113 const nsAttrValue* aValue, bool aNotify) { 1114 if (aNameSpaceID == kNameSpaceID_None) { 1115 if (aName == nsGkAtoms::disabled) { 1116 if (aNotify) { 1117 mDisabledChanged = true; 1118 } 1119 } else if (aName == nsGkAtoms::multiple) { 1120 if (!aValue && aNotify && mSelectedIndex >= 0) { 1121 // We're changing from being a multi-select to a single-select. 1122 // Make sure we only have one option selected before we do that. 1123 // Note that this needs to come before we really unset the attr, 1124 // since SetOptionsSelectedByIndex does some bail-out type 1125 // optimization for cases when the select is not multiple that 1126 // would lead to only a single option getting deselected. 1127 SetSelectedIndexInternal(mSelectedIndex, aNotify); 1128 } 1129 } 1130 } 1131 1132 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr( 1133 aNameSpaceID, aName, aValue, aNotify); 1134 } 1135 1136 void HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 1137 const nsAttrValue* aValue, 1138 const nsAttrValue* aOldValue, 1139 nsIPrincipal* aSubjectPrincipal, 1140 bool aNotify) { 1141 if (aNameSpaceID == kNameSpaceID_None) { 1142 if (aName == nsGkAtoms::disabled) { 1143 // This *has* to be called *before* validity state check because 1144 // UpdateBarredFromConstraintValidation and 1145 // UpdateValueMissingValidityState depend on our disabled state. 1146 UpdateDisabledState(aNotify); 1147 1148 UpdateValueMissingValidityState(); 1149 UpdateBarredFromConstraintValidation(); 1150 UpdateValidityElementStates(aNotify); 1151 } else if (aName == nsGkAtoms::required) { 1152 // This *has* to be called *before* UpdateValueMissingValidityState 1153 // because UpdateValueMissingValidityState depends on our required 1154 // state. 1155 UpdateRequiredState(!!aValue, aNotify); 1156 UpdateValueMissingValidityState(); 1157 UpdateValidityElementStates(aNotify); 1158 } else if (aName == nsGkAtoms::autocomplete) { 1159 // Clear the cached @autocomplete attribute and autocompleteInfo state. 1160 mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown; 1161 mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown; 1162 } else if (aName == nsGkAtoms::multiple) { 1163 if (!aValue && aNotify) { 1164 // We might have become a combobox; make sure _something_ gets 1165 // selected in that case 1166 CheckSelectSomething(aNotify); 1167 } 1168 } 1169 } 1170 1171 return nsGenericHTMLFormControlElementWithState::AfterSetAttr( 1172 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); 1173 } 1174 1175 void HTMLSelectElement::DoneAddingChildren(bool aHaveNotified) { 1176 mIsDoneAddingChildren = true; 1177 1178 nsISelectControlFrame* selectFrame = GetSelectFrame(); 1179 1180 // If we foolishly tried to restore before we were done adding 1181 // content, restore the rest of the options proper-like 1182 if (mRestoreState) { 1183 RestoreStateTo(*mRestoreState); 1184 mRestoreState = nullptr; 1185 } 1186 1187 // Notify the frame 1188 if (selectFrame) { 1189 selectFrame->DoneAddingChildren(true); 1190 } 1191 1192 if (!mInhibitStateRestoration) { 1193 GenerateStateKey(); 1194 RestoreFormControlState(); 1195 } 1196 1197 // Now that we're done, select something (if it's a single select something 1198 // must be selected) 1199 if (!CheckSelectSomething(false)) { 1200 // If an option has @selected set, it will be selected during parsing but 1201 // with an empty value. We have to make sure the select element updates it's 1202 // validity state to take this into account. 1203 UpdateValueMissingValidityState(); 1204 1205 // And now make sure we update our content state too 1206 UpdateValidityElementStates(aHaveNotified); 1207 } 1208 1209 mDefaultSelectionSet = true; 1210 } 1211 1212 bool HTMLSelectElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 1213 const nsAString& aValue, 1214 nsIPrincipal* aMaybeScriptedPrincipal, 1215 nsAttrValue& aResult) { 1216 if (kNameSpaceID_None == aNamespaceID) { 1217 if (aAttribute == nsGkAtoms::size) { 1218 return aResult.ParsePositiveIntValue(aValue); 1219 } 1220 if (aAttribute == nsGkAtoms::autocomplete) { 1221 aResult.ParseAtomArray(aValue); 1222 return true; 1223 } 1224 } 1225 return nsGenericHTMLFormControlElementWithState::ParseAttribute( 1226 aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult); 1227 } 1228 1229 void HTMLSelectElement::MapAttributesIntoRule( 1230 MappedDeclarationsBuilder& aBuilder) { 1231 nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto( 1232 aBuilder); 1233 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder); 1234 } 1235 1236 nsChangeHint HTMLSelectElement::GetAttributeChangeHint( 1237 const nsAtom* aAttribute, AttrModType aModType) const { 1238 nsChangeHint retval = 1239 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint( 1240 aAttribute, aModType); 1241 if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) { 1242 retval |= nsChangeHint_ReconstructFrame; 1243 } 1244 return retval; 1245 } 1246 1247 NS_IMETHODIMP_(bool) 1248 HTMLSelectElement::IsAttributeMapped(const nsAtom* aAttribute) const { 1249 static const MappedAttributeEntry* const map[] = {sCommonAttributeMap, 1250 sImageAlignAttributeMap}; 1251 1252 return FindAttributeDependence(aAttribute, map); 1253 } 1254 1255 nsMapRuleToAttributesFunc HTMLSelectElement::GetAttributeMappingFunction() 1256 const { 1257 return &MapAttributesIntoRule; 1258 } 1259 1260 bool HTMLSelectElement::IsDisabledForEvents(WidgetEvent* aEvent) { 1261 return IsElementDisabledForEvents(aEvent, GetPrimaryFrame()); 1262 } 1263 1264 void HTMLSelectElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { 1265 aVisitor.mCanHandle = false; 1266 if (IsDisabledForEvents(aVisitor.mEvent)) { 1267 return; 1268 } 1269 1270 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor); 1271 } 1272 1273 void HTMLSelectElement::UpdateValidityElementStates(bool aNotify) { 1274 AutoStateChangeNotifier notifier(*this, aNotify); 1275 RemoveStatesSilently(ElementState::VALIDITY_STATES); 1276 if (!IsCandidateForConstraintValidation()) { 1277 return; 1278 } 1279 1280 ElementState state; 1281 if (IsValid()) { 1282 state |= ElementState::VALID; 1283 if (mUserInteracted) { 1284 state |= ElementState::USER_VALID; 1285 } 1286 } else { 1287 state |= ElementState::INVALID; 1288 if (mUserInteracted) { 1289 state |= ElementState::USER_INVALID; 1290 } 1291 } 1292 1293 AddStatesSilently(state); 1294 } 1295 1296 void HTMLSelectElement::SaveState() { 1297 PresState* presState = GetPrimaryPresState(); 1298 if (!presState) { 1299 return; 1300 } 1301 1302 SelectContentData state; 1303 1304 uint32_t len = Length(); 1305 1306 for (uint32_t optIndex = 0; optIndex < len; optIndex++) { 1307 HTMLOptionElement* option = Item(optIndex); 1308 if (option && option->Selected()) { 1309 nsAutoString value; 1310 option->GetValue(value); 1311 if (value.IsEmpty()) { 1312 state.indices().AppendElement(optIndex); 1313 } else { 1314 state.values().AppendElement(std::move(value)); 1315 } 1316 } 1317 } 1318 1319 presState->contentData() = std::move(state); 1320 1321 if (mDisabledChanged) { 1322 // We do not want to save the real disabled state but the disabled 1323 // attribute. 1324 presState->disabled() = HasAttr(nsGkAtoms::disabled); 1325 presState->disabledSet() = true; 1326 } 1327 } 1328 1329 bool HTMLSelectElement::RestoreState(PresState* aState) { 1330 // Get the presentation state object to retrieve our stuff out of. 1331 const PresContentData& state = aState->contentData(); 1332 if (state.type() == PresContentData::TSelectContentData) { 1333 RestoreStateTo(state.get_SelectContentData()); 1334 1335 // Don't flush, if the frame doesn't exist yet it doesn't care if 1336 // we're reset or not. 1337 DispatchContentReset(); 1338 } 1339 1340 if (aState->disabledSet() && !aState->disabled()) { 1341 SetDisabled(false, IgnoreErrors()); 1342 } 1343 1344 return false; 1345 } 1346 1347 void HTMLSelectElement::RestoreStateTo(const SelectContentData& aNewSelected) { 1348 if (!mIsDoneAddingChildren) { 1349 // Make a copy of the state for us to restore from in the future. 1350 mRestoreState = MakeUnique<SelectContentData>(aNewSelected); 1351 return; 1352 } 1353 1354 uint32_t len = Length(); 1355 OptionFlags mask{OptionFlag::IsSelected, OptionFlag::ClearAll, 1356 OptionFlag::SetDisabled, OptionFlag::Notify}; 1357 1358 // First clear all 1359 SetOptionsSelectedByIndex(-1, -1, mask); 1360 1361 // Select by index. 1362 for (uint32_t idx : aNewSelected.indices()) { 1363 if (idx < len) { 1364 SetOptionsSelectedByIndex(idx, idx, 1365 {OptionFlag::IsSelected, 1366 OptionFlag::SetDisabled, OptionFlag::Notify}); 1367 } 1368 } 1369 1370 // Select by value. 1371 for (uint32_t i = 0; i < len; ++i) { 1372 HTMLOptionElement* option = Item(i); 1373 if (option) { 1374 nsAutoString value; 1375 option->GetValue(value); 1376 if (aNewSelected.values().Contains(value)) { 1377 SetOptionsSelectedByIndex( 1378 i, i, 1379 {OptionFlag::IsSelected, OptionFlag::SetDisabled, 1380 OptionFlag::Notify}); 1381 } 1382 } 1383 } 1384 } 1385 1386 // nsIFormControl 1387 1388 NS_IMETHODIMP 1389 HTMLSelectElement::Reset() { 1390 uint32_t numSelected = 0; 1391 1392 // 1393 // Cycle through the options array and reset the options 1394 // 1395 uint32_t numOptions = Length(); 1396 1397 for (uint32_t i = 0; i < numOptions; i++) { 1398 RefPtr<HTMLOptionElement> option = Item(i); 1399 if (option) { 1400 // 1401 // Reset the option to its default value 1402 // 1403 1404 OptionFlags mask = {OptionFlag::SetDisabled, OptionFlag::Notify, 1405 OptionFlag::NoReselect}; 1406 if (option->DefaultSelected()) { 1407 mask += OptionFlag::IsSelected; 1408 numSelected++; 1409 } 1410 1411 SetOptionsSelectedByIndex(i, i, mask); 1412 option->SetSelectedChanged(false); 1413 } 1414 } 1415 1416 // 1417 // If nothing was selected and it's not multiple, select something 1418 // 1419 if (numSelected == 0 && IsCombobox()) { 1420 SelectSomething(true); 1421 } 1422 1423 OnSelectionChanged(); 1424 SetUserInteracted(false); 1425 1426 // Let the frame know we were reset 1427 // 1428 // Don't flush, if there's no frame yet it won't care about us being 1429 // reset even if we forced it to be created now. 1430 // 1431 DispatchContentReset(); 1432 1433 return NS_OK; 1434 } 1435 1436 NS_IMETHODIMP 1437 HTMLSelectElement::SubmitNamesValues(FormData* aFormData) { 1438 // 1439 // Get the name (if no name, no submit) 1440 // 1441 nsAutoString name; 1442 GetAttr(nsGkAtoms::name, name); 1443 if (name.IsEmpty()) { 1444 return NS_OK; 1445 } 1446 1447 // 1448 // Submit 1449 // 1450 uint32_t len = Length(); 1451 1452 for (uint32_t optIndex = 0; optIndex < len; optIndex++) { 1453 HTMLOptionElement* option = Item(optIndex); 1454 1455 // Don't send disabled options 1456 if (!option || IsOptionDisabled(option)) { 1457 continue; 1458 } 1459 1460 if (!option->Selected()) { 1461 continue; 1462 } 1463 1464 nsString value; 1465 option->GetValue(value); 1466 1467 aFormData->AddNameValuePair(name, value); 1468 } 1469 1470 return NS_OK; 1471 } 1472 1473 void HTMLSelectElement::DispatchContentReset() { 1474 if (nsListControlFrame* listFrame = do_QueryFrame(GetPrimaryFrame())) { 1475 listFrame->OnContentReset(); 1476 } 1477 } 1478 1479 static void AddOptions(nsIContent* aRoot, HTMLOptionsCollection* aArray) { 1480 for (nsIContent* child = aRoot->GetFirstChild(); child; 1481 child = child->GetNextSibling()) { 1482 HTMLOptionElement* opt = HTMLOptionElement::FromNode(child); 1483 if (opt) { 1484 aArray->AppendOption(opt); 1485 } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) { 1486 for (nsIContent* grandchild = child->GetFirstChild(); grandchild; 1487 grandchild = grandchild->GetNextSibling()) { 1488 opt = HTMLOptionElement::FromNode(grandchild); 1489 if (opt) { 1490 aArray->AppendOption(opt); 1491 } 1492 } 1493 } 1494 } 1495 } 1496 1497 void HTMLSelectElement::RebuildOptionsArray(bool aNotify) { 1498 mOptions->Clear(); 1499 AddOptions(this, mOptions); 1500 FindSelectedIndex(0, aNotify); 1501 } 1502 1503 bool HTMLSelectElement::IsValueMissing() const { 1504 if (!Required()) { 1505 return false; 1506 } 1507 1508 uint32_t length = Length(); 1509 1510 for (uint32_t i = 0; i < length; ++i) { 1511 RefPtr<HTMLOptionElement> option = Item(i); 1512 // Check for a placeholder label option, don't count it as a valid value. 1513 if (i == 0 && !Multiple() && Size() <= 1 && option->GetParent() == this) { 1514 nsAutoString value; 1515 option->GetValue(value); 1516 if (value.IsEmpty()) { 1517 continue; 1518 } 1519 } 1520 1521 if (!option->Selected()) { 1522 continue; 1523 } 1524 1525 return false; 1526 } 1527 1528 return true; 1529 } 1530 1531 void HTMLSelectElement::UpdateValueMissingValidityState() { 1532 SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing()); 1533 } 1534 1535 nsresult HTMLSelectElement::GetValidationMessage(nsAString& aValidationMessage, 1536 ValidityStateType aType) { 1537 switch (aType) { 1538 case VALIDITY_STATE_VALUE_MISSING: { 1539 nsAutoString message; 1540 nsresult rv = nsContentUtils::GetMaybeLocalizedString( 1541 nsContentUtils::eDOM_PROPERTIES, "FormValidationSelectMissing", 1542 OwnerDoc(), message); 1543 aValidationMessage = message; 1544 return rv; 1545 } 1546 default: { 1547 return ConstraintValidation::GetValidationMessage(aValidationMessage, 1548 aType); 1549 } 1550 } 1551 } 1552 1553 #ifdef DEBUG 1554 1555 void HTMLSelectElement::VerifyOptionsArray() { 1556 int32_t index = 0; 1557 for (nsIContent* child = nsINode::GetFirstChild(); child; 1558 child = child->GetNextSibling()) { 1559 HTMLOptionElement* opt = HTMLOptionElement::FromNode(child); 1560 if (opt) { 1561 NS_ASSERTION(opt == mOptions->ItemAsOption(index++), 1562 "Options collection broken"); 1563 } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) { 1564 for (nsIContent* grandchild = child->GetFirstChild(); grandchild; 1565 grandchild = grandchild->GetNextSibling()) { 1566 opt = HTMLOptionElement::FromNode(grandchild); 1567 if (opt) { 1568 NS_ASSERTION(opt == mOptions->ItemAsOption(index++), 1569 "Options collection broken"); 1570 } 1571 } 1572 } 1573 } 1574 } 1575 1576 #endif 1577 1578 void HTMLSelectElement::UpdateBarredFromConstraintValidation() { 1579 SetBarredFromConstraintValidation( 1580 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled()); 1581 } 1582 1583 void HTMLSelectElement::FieldSetDisabledChanged(bool aNotify) { 1584 // This *has* to be called before UpdateBarredFromConstraintValidation and 1585 // UpdateValueMissingValidityState because these two functions depend on our 1586 // disabled state. 1587 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify); 1588 1589 UpdateValueMissingValidityState(); 1590 UpdateBarredFromConstraintValidation(); 1591 UpdateValidityElementStates(aNotify); 1592 } 1593 1594 void HTMLSelectElement::OnSelectionChanged() { 1595 if (!mDefaultSelectionSet) { 1596 return; 1597 } 1598 1599 if (State().HasState(ElementState::AUTOFILL)) { 1600 RemoveStates(ElementState::AUTOFILL | ElementState::AUTOFILL_PREVIEW); 1601 } 1602 1603 UpdateSelectedOptions(); 1604 } 1605 1606 void HTMLSelectElement::UpdateSelectedOptions() { 1607 if (mSelectedOptions) { 1608 mSelectedOptions->SetDirty(); 1609 } 1610 } 1611 1612 void HTMLSelectElement::SetUserInteracted(bool aInteracted) { 1613 if (mUserInteracted == aInteracted) { 1614 return; 1615 } 1616 mUserInteracted = aInteracted; 1617 UpdateValidityElementStates(true); 1618 } 1619 1620 void HTMLSelectElement::SetPreviewValue(const nsAString& aValue) { 1621 mPreviewValue = aValue; 1622 nsContentUtils::RemoveNewlines(mPreviewValue); 1623 nsComboboxControlFrame* comboFrame = do_QueryFrame(GetPrimaryFrame()); 1624 if (comboFrame) { 1625 comboFrame->RedisplaySelectedText(); 1626 } 1627 } 1628 1629 void HTMLSelectElement::UserFinishedInteracting(bool aChanged) { 1630 SetUserInteracted(true); 1631 if (!aChanged) { 1632 return; 1633 } 1634 1635 // Dispatch the input event. 1636 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this); 1637 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1638 "Failed to dispatch input event"); 1639 1640 // Dispatch the change event. 1641 nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u"change"_ns, 1642 CanBubble::eYes, Cancelable::eNo); 1643 } 1644 1645 JSObject* HTMLSelectElement::WrapNode(JSContext* aCx, 1646 JS::Handle<JSObject*> aGivenProto) { 1647 return HTMLSelectElement_Binding::Wrap(aCx, this, aGivenProto); 1648 } 1649 1650 } // namespace mozilla::dom