Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / html / HTMLSelectElement.cpp
blob6ca4209cd9679d2af7d92b15c4a9f5e8076029f3
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/HTMLSelectElement.h"
9 #include "mozAutoDocUpdate.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/BasicEvents.h"
12 #include "mozilla/EventDispatcher.h"
13 #include "mozilla/dom/Element.h"
14 #include "mozilla/dom/FormData.h"
15 #include "mozilla/dom/HTMLOptGroupElement.h"
16 #include "mozilla/dom/HTMLOptionElement.h"
17 #include "mozilla/dom/HTMLSelectElementBinding.h"
18 #include "mozilla/dom/UnionTypes.h"
19 #include "mozilla/dom/WindowGlobalChild.h"
20 #include "mozilla/MappedDeclarationsBuilder.h"
21 #include "mozilla/Maybe.h"
22 #include "mozilla/Unused.h"
23 #include "nsContentCreatorFunctions.h"
24 #include "nsContentList.h"
25 #include "nsContentUtils.h"
26 #include "nsError.h"
27 #include "nsGkAtoms.h"
28 #include "nsComboboxControlFrame.h"
29 #include "mozilla/dom/Document.h"
30 #include "nsIFormControlFrame.h"
31 #include "nsIFrame.h"
32 #include "nsListControlFrame.h"
33 #include "nsISelectControlFrame.h"
34 #include "nsLayoutUtils.h"
35 #include "mozilla/PresState.h"
36 #include "nsServiceManagerUtils.h"
37 #include "nsStyleConsts.h"
38 #include "nsTextNode.h"
40 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Select)
42 namespace mozilla::dom {
44 //----------------------------------------------------------------------
46 // SafeOptionListMutation
49 SafeOptionListMutation::SafeOptionListMutation(nsIContent* aSelect,
50 nsIContent* aParent,
51 nsIContent* aKid,
52 uint32_t aIndex, bool aNotify)
53 : mSelect(HTMLSelectElement::FromNodeOrNull(aSelect)),
54 mTopLevelMutation(false),
55 mNeedsRebuild(false),
56 mNotify(aNotify) {
57 if (mSelect) {
58 mInitialSelectedOption = mSelect->Item(mSelect->SelectedIndex());
59 mTopLevelMutation = !mSelect->mMutating;
60 if (mTopLevelMutation) {
61 mSelect->mMutating = true;
62 } else {
63 // This is very unfortunate, but to handle mutation events properly,
64 // option list must be up-to-date before inserting or removing options.
65 // Fortunately this is called only if mutation event listener
66 // adds or removes options.
67 mSelect->RebuildOptionsArray(mNotify);
69 nsresult rv;
70 if (aKid) {
71 rv = mSelect->WillAddOptions(aKid, aParent, aIndex, mNotify);
72 } else {
73 rv = mSelect->WillRemoveOptions(aParent, aIndex, mNotify);
75 mNeedsRebuild = NS_FAILED(rv);
79 SafeOptionListMutation::~SafeOptionListMutation() {
80 if (mSelect) {
81 if (mNeedsRebuild || (mTopLevelMutation && mGuard.Mutated(1))) {
82 mSelect->RebuildOptionsArray(true);
84 if (mTopLevelMutation) {
85 mSelect->mMutating = false;
87 if (mSelect->Item(mSelect->SelectedIndex()) != mInitialSelectedOption) {
88 // We must have triggered the SelectSomething() codepath, which can cause
89 // our validity to change. Unfortunately, our attempt to update validity
90 // in that case may not have worked correctly, because we actually call it
91 // before we have inserted the new <option>s into the DOM! Go ahead and
92 // update validity here as needed, because by now we know our <option>s
93 // are where they should be.
94 mSelect->UpdateValueMissingValidityState();
95 mSelect->UpdateValidityElementStates(mNotify);
97 #ifdef DEBUG
98 mSelect->VerifyOptionsArray();
99 #endif
103 //----------------------------------------------------------------------
105 // HTMLSelectElement
108 // construction, destruction
110 HTMLSelectElement::HTMLSelectElement(
111 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
112 FromParser aFromParser)
113 : nsGenericHTMLFormControlElementWithState(
114 std::move(aNodeInfo), aFromParser, FormControlType::Select),
115 mOptions(new HTMLOptionsCollection(this)),
116 mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown),
117 mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown),
118 mIsDoneAddingChildren(!aFromParser),
119 mDisabledChanged(false),
120 mMutating(false),
121 mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
122 mUserInteracted(false),
123 mDefaultSelectionSet(false),
124 mIsOpenInParentProcess(false),
125 mNonOptionChildren(0),
126 mOptGroupCount(0),
127 mSelectedIndex(-1) {
128 SetHasWeirdParserInsertionMode();
130 // DoneAddingChildren() will be called later if it's from the parser,
131 // otherwise it is
133 // Set up our default state: enabled, optional, and valid.
134 AddStatesSilently(ElementState::ENABLED | ElementState::OPTIONAL_ |
135 ElementState::VALID);
138 // ISupports
140 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLSelectElement)
142 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
143 HTMLSelectElement, nsGenericHTMLFormControlElementWithState)
144 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
145 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOptions)
146 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedOptions)
147 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
148 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
149 HTMLSelectElement, nsGenericHTMLFormControlElementWithState)
150 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
151 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedOptions)
152 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
154 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(
155 HTMLSelectElement, nsGenericHTMLFormControlElementWithState,
156 nsIConstraintValidation)
158 // nsIDOMHTMLSelectElement
160 NS_IMPL_ELEMENT_CLONE(HTMLSelectElement)
162 void HTMLSelectElement::SetCustomValidity(const nsAString& aError) {
163 ConstraintValidation::SetCustomValidity(aError);
164 UpdateValidityElementStates(true);
167 // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
168 void HTMLSelectElement::ShowPicker(ErrorResult& aRv) {
169 // Step 1. If this is not mutable, then throw an "InvalidStateError"
170 // DOMException.
171 if (IsDisabled()) {
172 return aRv.ThrowInvalidStateError("This select is disabled.");
175 // Step 2. If this's relevant settings object's origin is not same origin with
176 // this's relevant settings object's top-level origin, and this is a select
177 // element, [...], then throw a "SecurityError" DOMException.
178 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
179 WindowGlobalChild* windowGlobalChild =
180 window ? window->GetWindowGlobalChild() : nullptr;
181 if (!windowGlobalChild || !windowGlobalChild->SameOriginWithTop()) {
182 return aRv.ThrowSecurityError(
183 "Call was blocked because the current origin isn't same-origin with "
184 "top.");
187 // Step 3. If this's relevant global object does not have transient
188 // activation, then throw a "NotAllowedError" DOMException.
189 if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
190 return aRv.ThrowNotAllowedError(
191 "Call was blocked due to lack of user activation.");
194 // Step 4. If this is a select element, and this is not being rendered, then
195 // throw a "NotSupportedError" DOMException.
197 // Flush frames so that IsRendered returns up-to-date results.
198 Unused << GetPrimaryFrame(FlushType::Frames);
199 if (!IsRendered()) {
200 return aRv.ThrowNotSupportedError("This select isn't being rendered.");
203 // Step 5. Show the picker, if applicable, for this.
204 #if !defined(ANDROID)
205 if (!IsCombobox()) {
206 return;
208 #endif
209 if (!OpenInParentProcess()) {
210 RefPtr<Document> doc = OwnerDoc();
211 nsContentUtils::DispatchChromeEvent(doc, this, u"mozshowdropdown"_ns,
212 CanBubble::eYes, Cancelable::eNo);
216 void HTMLSelectElement::GetAutocomplete(DOMString& aValue) {
217 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
219 mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(
220 attributeVal, aValue, mAutocompleteAttrState);
223 void HTMLSelectElement::GetAutocompleteInfo(AutocompleteInfo& aInfo) {
224 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
225 mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute(
226 attributeVal, aInfo, mAutocompleteInfoState, true);
229 void HTMLSelectElement::InsertChildBefore(nsIContent* aKid,
230 nsIContent* aBeforeThis, bool aNotify,
231 ErrorResult& aRv) {
232 const uint32_t index =
233 aBeforeThis ? *ComputeIndexOf(aBeforeThis) : GetChildCount();
234 SafeOptionListMutation safeMutation(this, this, aKid, index, aNotify);
235 nsGenericHTMLFormControlElementWithState::InsertChildBefore(aKid, aBeforeThis,
236 aNotify, aRv);
237 if (aRv.Failed()) {
238 safeMutation.MutationFailed();
242 void HTMLSelectElement::RemoveChildNode(nsIContent* aKid, bool aNotify) {
243 SafeOptionListMutation safeMutation(this, this, nullptr,
244 *ComputeIndexOf(aKid), aNotify);
245 nsGenericHTMLFormControlElementWithState::RemoveChildNode(aKid, aNotify);
248 void HTMLSelectElement::InsertOptionsIntoList(nsIContent* aOptions,
249 int32_t aListIndex,
250 int32_t aDepth, bool aNotify) {
251 MOZ_ASSERT(aDepth == 0 || aDepth == 1);
252 int32_t insertIndex = aListIndex;
254 HTMLOptionElement* optElement = HTMLOptionElement::FromNode(aOptions);
255 if (optElement) {
256 mOptions->InsertOptionAt(optElement, insertIndex);
257 insertIndex++;
258 } else if (aDepth == 0) {
259 // If it's at the top level, then we just found out there are non-options
260 // at the top level, which will throw off the insert count
261 mNonOptionChildren++;
263 // Deal with optgroups
264 if (aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
265 mOptGroupCount++;
267 for (nsIContent* child = aOptions->GetFirstChild(); child;
268 child = child->GetNextSibling()) {
269 optElement = HTMLOptionElement::FromNode(child);
270 if (optElement) {
271 mOptions->InsertOptionAt(optElement, insertIndex);
272 insertIndex++;
276 } // else ignore even if optgroup; we want to ignore nested optgroups.
278 // Deal with the selected list
279 if (insertIndex - aListIndex) {
280 // Fix the currently selected index
281 if (aListIndex <= mSelectedIndex) {
282 mSelectedIndex += (insertIndex - aListIndex);
283 OnSelectionChanged();
286 // Get the frame stuff for notification. No need to flush here
287 // since if there's no frame for the select yet the select will
288 // get into the right state once it's created.
289 nsISelectControlFrame* selectFrame = nullptr;
290 AutoWeakFrame weakSelectFrame;
291 bool didGetFrame = false;
293 // Actually select the options if the added options warrant it
294 for (int32_t i = aListIndex; i < insertIndex; i++) {
295 // Notify the frame that the option is added
296 if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
297 selectFrame = GetSelectFrame();
298 weakSelectFrame = do_QueryFrame(selectFrame);
299 didGetFrame = true;
302 if (selectFrame) {
303 selectFrame->AddOption(i);
306 RefPtr<HTMLOptionElement> option = Item(i);
307 if (option && option->Selected()) {
308 // Clear all other options
309 if (!HasAttr(nsGkAtoms::multiple)) {
310 OptionFlags mask{OptionFlag::IsSelected, OptionFlag::ClearAll,
311 OptionFlag::SetDisabled, OptionFlag::Notify,
312 OptionFlag::InsertingOptions};
313 SetOptionsSelectedByIndex(i, i, mask);
316 // This is sort of a hack ... we need to notify that the option was
317 // set and change selectedIndex even though we didn't really change
318 // its value.
319 OnOptionSelected(selectFrame, i, true, false, aNotify);
323 CheckSelectSomething(aNotify);
327 nsresult HTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions,
328 int32_t aListIndex,
329 int32_t aDepth,
330 bool aNotify) {
331 MOZ_ASSERT(aDepth == 0 || aDepth == 1);
332 int32_t numRemoved = 0;
334 HTMLOptionElement* optElement = HTMLOptionElement::FromNode(aOptions);
335 if (optElement) {
336 if (mOptions->ItemAsOption(aListIndex) != optElement) {
337 NS_ERROR("wrong option at index");
338 return NS_ERROR_UNEXPECTED;
340 mOptions->RemoveOptionAt(aListIndex);
341 numRemoved++;
342 } else if (aDepth == 0) {
343 // Yay, one less artifact at the top level.
344 mNonOptionChildren--;
346 // Recurse down deeper for options
347 if (mOptGroupCount && aOptions->IsHTMLElement(nsGkAtoms::optgroup)) {
348 mOptGroupCount--;
350 for (nsIContent* child = aOptions->GetFirstChild(); child;
351 child = child->GetNextSibling()) {
352 optElement = HTMLOptionElement::FromNode(child);
353 if (optElement) {
354 if (mOptions->ItemAsOption(aListIndex) != optElement) {
355 NS_ERROR("wrong option at index");
356 return NS_ERROR_UNEXPECTED;
358 mOptions->RemoveOptionAt(aListIndex);
359 numRemoved++;
363 } // else don't check for an optgroup; we want to ignore nested optgroups
365 if (numRemoved) {
366 // Tell the widget we removed the options
367 nsISelectControlFrame* selectFrame = GetSelectFrame();
368 if (selectFrame) {
369 nsAutoScriptBlocker scriptBlocker;
370 for (int32_t i = aListIndex; i < aListIndex + numRemoved; ++i) {
371 selectFrame->RemoveOption(i);
375 // Fix the selected index
376 if (aListIndex <= mSelectedIndex) {
377 if (mSelectedIndex < (aListIndex + numRemoved)) {
378 // aListIndex <= mSelectedIndex < aListIndex+numRemoved
379 // Find a new selected index if it was one of the ones removed.
380 // If this is a Combobox, no other Item will be selected.
381 if (IsCombobox()) {
382 mSelectedIndex = -1;
383 OnSelectionChanged();
384 } else {
385 FindSelectedIndex(aListIndex, aNotify);
387 } else {
388 // Shift the selected index if something in front of it was removed
389 // aListIndex+numRemoved <= mSelectedIndex
390 mSelectedIndex -= numRemoved;
391 OnSelectionChanged();
395 // Select something in case we removed the selected option on a
396 // single select
397 if (!CheckSelectSomething(aNotify) && mSelectedIndex == -1) {
398 // Update the validity state in case of we've just removed the last
399 // option.
400 UpdateValueMissingValidityState();
401 UpdateValidityElementStates(aNotify);
405 return NS_OK;
408 // XXXldb Doing the processing before the content nodes have been added
409 // to the document (as the name of this function seems to require, and
410 // as the callers do), is highly unusual. Passing around unparented
411 // content to other parts of the app can make those things think the
412 // options are the root content node.
413 NS_IMETHODIMP
414 HTMLSelectElement::WillAddOptions(nsIContent* aOptions, nsIContent* aParent,
415 int32_t aContentIndex, bool aNotify) {
416 if (this != aParent && this != aParent->GetParent()) {
417 return NS_OK;
419 int32_t level = aParent == this ? 0 : 1;
421 // Get the index where the options will be inserted
422 int32_t ind = -1;
423 if (!mNonOptionChildren) {
424 // If there are no artifacts, aContentIndex == ind
425 ind = aContentIndex;
426 } else {
427 // If there are artifacts, we have to get the index of the option the
428 // hard way
429 int32_t children = aParent->GetChildCount();
431 if (aContentIndex >= children) {
432 // If the content insert is after the end of the parent, then we want to
433 // get the next index *after* the parent and insert there.
434 ind = GetOptionIndexAfter(aParent);
435 } else {
436 // If the content insert is somewhere in the middle of the container, then
437 // we want to get the option currently at the index and insert in front of
438 // that.
439 nsIContent* currentKid = aParent->GetChildAt_Deprecated(aContentIndex);
440 NS_ASSERTION(currentKid, "Child not found!");
441 if (currentKid) {
442 ind = GetOptionIndexAt(currentKid);
443 } else {
444 ind = -1;
449 InsertOptionsIntoList(aOptions, ind, level, aNotify);
450 return NS_OK;
453 NS_IMETHODIMP
454 HTMLSelectElement::WillRemoveOptions(nsIContent* aParent, int32_t aContentIndex,
455 bool aNotify) {
456 if (this != aParent && this != aParent->GetParent()) {
457 return NS_OK;
459 int32_t level = this == aParent ? 0 : 1;
461 // Get the index where the options will be removed
462 nsIContent* currentKid = aParent->GetChildAt_Deprecated(aContentIndex);
463 if (currentKid) {
464 int32_t ind;
465 if (!mNonOptionChildren) {
466 // If there are no artifacts, aContentIndex == ind
467 ind = aContentIndex;
468 } else {
469 // If there are artifacts, we have to get the index of the option the
470 // hard way
471 ind = GetFirstOptionIndex(currentKid);
473 if (ind != -1) {
474 nsresult rv = RemoveOptionsFromList(currentKid, ind, level, aNotify);
475 NS_ENSURE_SUCCESS(rv, rv);
479 return NS_OK;
482 int32_t HTMLSelectElement::GetOptionIndexAt(nsIContent* aOptions) {
483 // Search this node and below.
484 // If not found, find the first one *after* this node.
485 int32_t retval = GetFirstOptionIndex(aOptions);
486 if (retval == -1) {
487 retval = GetOptionIndexAfter(aOptions);
490 return retval;
493 int32_t HTMLSelectElement::GetOptionIndexAfter(nsIContent* aOptions) {
494 // - If this is the select, the next option is the last.
495 // - If not, search all the options after aOptions and up to the last option
496 // in the parent.
497 // - If it's not there, search for the first option after the parent.
498 if (aOptions == this) {
499 return Length();
502 int32_t retval = -1;
504 nsCOMPtr<nsIContent> parent = aOptions->GetParent();
506 if (parent) {
507 const int32_t index = parent->ComputeIndexOf_Deprecated(aOptions);
508 const int32_t count = static_cast<int32_t>(parent->GetChildCount());
510 retval = GetFirstChildOptionIndex(parent, index + 1, count);
512 if (retval == -1) {
513 retval = GetOptionIndexAfter(parent);
517 return retval;
520 int32_t HTMLSelectElement::GetFirstOptionIndex(nsIContent* aOptions) {
521 int32_t listIndex = -1;
522 HTMLOptionElement* optElement = HTMLOptionElement::FromNode(aOptions);
523 if (optElement) {
524 mOptions->GetOptionIndex(optElement, 0, true, &listIndex);
525 return listIndex;
528 listIndex = GetFirstChildOptionIndex(aOptions, 0, aOptions->GetChildCount());
530 return listIndex;
533 int32_t HTMLSelectElement::GetFirstChildOptionIndex(nsIContent* aOptions,
534 int32_t aStartIndex,
535 int32_t aEndIndex) {
536 int32_t retval = -1;
538 for (int32_t i = aStartIndex; i < aEndIndex; ++i) {
539 retval = GetFirstOptionIndex(aOptions->GetChildAt_Deprecated(i));
540 if (retval != -1) {
541 break;
545 return retval;
548 nsISelectControlFrame* HTMLSelectElement::GetSelectFrame() {
549 nsIFormControlFrame* form_control_frame = GetFormControlFrame(false);
551 nsISelectControlFrame* select_frame = nullptr;
553 if (form_control_frame) {
554 select_frame = do_QueryFrame(form_control_frame);
557 return select_frame;
560 void HTMLSelectElement::Add(
561 const HTMLOptionElementOrHTMLOptGroupElement& aElement,
562 const Nullable<HTMLElementOrLong>& aBefore, ErrorResult& aRv) {
563 nsGenericHTMLElement& element =
564 aElement.IsHTMLOptionElement() ? static_cast<nsGenericHTMLElement&>(
565 aElement.GetAsHTMLOptionElement())
566 : static_cast<nsGenericHTMLElement&>(
567 aElement.GetAsHTMLOptGroupElement());
569 if (aBefore.IsNull()) {
570 Add(element, static_cast<nsGenericHTMLElement*>(nullptr), aRv);
571 } else if (aBefore.Value().IsHTMLElement()) {
572 Add(element, &aBefore.Value().GetAsHTMLElement(), aRv);
573 } else {
574 Add(element, aBefore.Value().GetAsLong(), aRv);
578 void HTMLSelectElement::Add(nsGenericHTMLElement& aElement,
579 nsGenericHTMLElement* aBefore,
580 ErrorResult& aError) {
581 if (!aBefore) {
582 Element::AppendChild(aElement, aError);
583 return;
586 // Just in case we're not the parent, get the parent of the reference
587 // element
588 nsCOMPtr<nsINode> parent = aBefore->Element::GetParentNode();
589 if (!parent || !parent->IsInclusiveDescendantOf(this)) {
590 // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT
591 // element.
592 aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
593 return;
596 // If the before parameter is not null, we are equivalent to the
597 // insertBefore method on the parent of before.
598 nsCOMPtr<nsINode> refNode = aBefore;
599 parent->InsertBefore(aElement, refNode, aError);
602 void HTMLSelectElement::Remove(int32_t aIndex) const {
603 if (aIndex < 0) {
604 return;
607 nsCOMPtr<nsINode> option = Item(static_cast<uint32_t>(aIndex));
608 if (!option) {
609 return;
612 option->Remove();
615 void HTMLSelectElement::GetType(nsAString& aType) {
616 if (HasAttr(nsGkAtoms::multiple)) {
617 aType.AssignLiteral("select-multiple");
618 } else {
619 aType.AssignLiteral("select-one");
623 void HTMLSelectElement::SetLength(uint32_t aLength, ErrorResult& aRv) {
624 constexpr uint32_t kMaxDynamicSelectLength = 100000;
626 uint32_t curlen = Length();
628 if (curlen > aLength) { // Remove extra options
629 for (uint32_t i = curlen; i > aLength; --i) {
630 Remove(i - 1);
632 } else if (aLength > curlen) {
633 if (aLength > kMaxDynamicSelectLength) {
634 nsAutoString strOptionsLength;
635 strOptionsLength.AppendInt(aLength);
637 nsAutoString strLimit;
638 strLimit.AppendInt(kMaxDynamicSelectLength);
640 nsContentUtils::ReportToConsole(
641 nsIScriptError::warningFlag, "DOM"_ns, OwnerDoc(),
642 nsContentUtils::eDOM_PROPERTIES,
643 "SelectOptionsLengthAssignmentWarning", {strOptionsLength, strLimit});
644 return;
647 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
649 nsContentUtils::QNameChanged(mNodeInfo, nsGkAtoms::option,
650 getter_AddRefs(nodeInfo));
652 nsCOMPtr<nsINode> node = NS_NewHTMLOptionElement(nodeInfo.forget());
653 for (uint32_t i = curlen; i < aLength; i++) {
654 nsINode::AppendChild(*node, aRv);
655 if (aRv.Failed()) {
656 return;
659 if (i + 1 < aLength) {
660 node = node->CloneNode(true, aRv);
661 if (aRv.Failed()) {
662 return;
664 MOZ_ASSERT(node);
670 /* static */
671 bool HTMLSelectElement::MatchSelectedOptions(Element* aElement,
672 int32_t /* unused */,
673 nsAtom* /* unused */,
674 void* /* unused*/) {
675 HTMLOptionElement* option = HTMLOptionElement::FromNode(aElement);
676 return option && option->Selected();
679 nsIHTMLCollection* HTMLSelectElement::SelectedOptions() {
680 if (!mSelectedOptions) {
681 mSelectedOptions = new nsContentList(this, MatchSelectedOptions, nullptr,
682 nullptr, /* deep */ true);
684 return mSelectedOptions;
687 void HTMLSelectElement::SetSelectedIndexInternal(int32_t aIndex, bool aNotify) {
688 int32_t oldSelectedIndex = mSelectedIndex;
689 OptionFlags mask{OptionFlag::IsSelected, OptionFlag::ClearAll,
690 OptionFlag::SetDisabled};
691 if (aNotify) {
692 mask += OptionFlag::Notify;
695 SetOptionsSelectedByIndex(aIndex, aIndex, mask);
697 nsISelectControlFrame* selectFrame = GetSelectFrame();
698 if (selectFrame) {
699 selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex);
702 OnSelectionChanged();
705 bool HTMLSelectElement::IsOptionSelectedByIndex(int32_t aIndex) const {
706 HTMLOptionElement* option = Item(static_cast<uint32_t>(aIndex));
707 return option && option->Selected();
710 void HTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame,
711 int32_t aIndex, bool aSelected,
712 bool aChangeOptionState,
713 bool aNotify) {
714 // Set the selected index
715 if (aSelected && (aIndex < mSelectedIndex || mSelectedIndex < 0)) {
716 mSelectedIndex = aIndex;
717 OnSelectionChanged();
718 } else if (!aSelected && aIndex == mSelectedIndex) {
719 FindSelectedIndex(aIndex + 1, aNotify);
722 if (aChangeOptionState) {
723 // Tell the option to get its bad self selected
724 RefPtr<HTMLOptionElement> option = Item(static_cast<uint32_t>(aIndex));
725 if (option) {
726 option->SetSelectedInternal(aSelected, aNotify);
730 // Let the frame know too
731 if (aSelectFrame) {
732 aSelectFrame->OnOptionSelected(aIndex, aSelected);
735 UpdateSelectedOptions();
736 UpdateValueMissingValidityState();
737 UpdateValidityElementStates(aNotify);
740 void HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex, bool aNotify) {
741 mSelectedIndex = -1;
742 uint32_t len = Length();
743 for (int32_t i = aStartIndex; i < int32_t(len); i++) {
744 if (IsOptionSelectedByIndex(i)) {
745 mSelectedIndex = i;
746 break;
749 OnSelectionChanged();
752 // XXX Consider splitting this into two functions for ease of reading:
753 // SelectOptionsByIndex(startIndex, endIndex, clearAll, checkDisabled)
754 // startIndex, endIndex - the range of options to turn on
755 // (-1, -1) will clear all indices no matter what.
756 // clearAll - will clear all other options unless checkDisabled is on
757 // and all the options attempted to be set are disabled
758 // (note that if it is not multiple, and an option is selected,
759 // everything else will be cleared regardless).
760 // checkDisabled - if this is TRUE, and an option is disabled, it will not be
761 // changed regardless of whether it is selected or not.
762 // Generally the UI passes TRUE and JS passes FALSE.
763 // (setDisabled currently is the opposite)
764 // DeselectOptionsByIndex(startIndex, endIndex, checkDisabled)
765 // startIndex, endIndex - the range of options to turn on
766 // (-1, -1) will clear all indices no matter what.
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)
772 // XXXbz the above comment is pretty confusing. Maybe we should actually
773 // document the args to this function too, in addition to documenting what
774 // things might end up looking like? In particular, pay attention to the
775 // setDisabled vs checkDisabled business.
776 bool HTMLSelectElement::SetOptionsSelectedByIndex(int32_t aStartIndex,
777 int32_t aEndIndex,
778 OptionFlags aOptionsMask) {
779 #if 0
780 printf("SetOption(%d-%d, %c, ClearAll=%c)\n", aStartIndex, aEndIndex,
781 (aOptionsMask.contains(OptionFlag::IsSelected) ? 'Y' : 'N'),
782 (aOptionsMask.contains(OptionFlag::ClearAll) ? 'Y' : 'N'));
783 #endif
784 // Don't bother if the select is disabled
785 if (!aOptionsMask.contains(OptionFlag::SetDisabled) && IsDisabled()) {
786 return false;
789 // Don't bother if there are no options
790 uint32_t numItems = Length();
791 if (numItems == 0) {
792 return false;
795 // First, find out whether multiple items can be selected
796 bool isMultiple = Multiple();
798 // These variables tell us whether any options were selected
799 // or deselected.
800 bool optionsSelected = false;
801 bool optionsDeselected = false;
803 nsISelectControlFrame* selectFrame = nullptr;
804 bool didGetFrame = false;
805 AutoWeakFrame weakSelectFrame;
807 if (aOptionsMask.contains(OptionFlag::IsSelected)) {
808 // Setting selectedIndex to an out-of-bounds index means -1. (HTML5)
809 if (aStartIndex < 0 || AssertedCast<uint32_t>(aStartIndex) >= numItems ||
810 aEndIndex < 0 || AssertedCast<uint32_t>(aEndIndex) >= numItems) {
811 aStartIndex = -1;
812 aEndIndex = -1;
815 // Only select the first value if it's not multiple
816 if (!isMultiple) {
817 aEndIndex = aStartIndex;
820 // This variable tells whether or not all of the options we attempted to
821 // select are disabled. If ClearAll is passed in as true, and we do not
822 // select anything because the options are disabled, we will not clear the
823 // other options. (This is to make the UI work the way one might expect.)
824 bool allDisabled = !aOptionsMask.contains(OptionFlag::SetDisabled);
827 // Save a little time when clearing other options
829 int32_t previousSelectedIndex = mSelectedIndex;
832 // Select the requested indices
834 // If index is -1, everything will be deselected (bug 28143)
835 if (aStartIndex != -1) {
836 MOZ_ASSERT(aStartIndex >= 0);
837 MOZ_ASSERT(aEndIndex >= 0);
838 // Loop through the options and select them (if they are not disabled and
839 // if they are not already selected).
840 for (uint32_t optIndex = AssertedCast<uint32_t>(aStartIndex);
841 optIndex <= AssertedCast<uint32_t>(aEndIndex); optIndex++) {
842 RefPtr<HTMLOptionElement> option = Item(optIndex);
844 // Ignore disabled options.
845 if (!aOptionsMask.contains(OptionFlag::SetDisabled)) {
846 if (option && IsOptionDisabled(option)) {
847 continue;
849 allDisabled = false;
852 // If the index is already selected, ignore it. On the other hand when
853 // the option has just been inserted we have to get in sync with it.
854 if (option && (aOptionsMask.contains(OptionFlag::InsertingOptions) ||
855 !option->Selected())) {
856 // To notify the frame if anything gets changed. No need
857 // to flush here, if there's no frame yet we don't need to
858 // force it to be created just to notify it about a change
859 // in the select.
860 selectFrame = GetSelectFrame();
861 weakSelectFrame = do_QueryFrame(selectFrame);
862 didGetFrame = true;
864 OnOptionSelected(selectFrame, optIndex, true, !option->Selected(),
865 aOptionsMask.contains(OptionFlag::Notify));
866 optionsSelected = true;
871 // Next remove all other options if single select or all is clear
872 // If index is -1, everything will be deselected (bug 28143)
873 if (((!isMultiple && optionsSelected) ||
874 (aOptionsMask.contains(OptionFlag::ClearAll) && !allDisabled) ||
875 aStartIndex == -1) &&
876 previousSelectedIndex != -1) {
877 for (uint32_t optIndex = AssertedCast<uint32_t>(previousSelectedIndex);
878 optIndex < numItems; optIndex++) {
879 if (static_cast<int32_t>(optIndex) < aStartIndex ||
880 static_cast<int32_t>(optIndex) > aEndIndex) {
881 HTMLOptionElement* option = Item(optIndex);
882 // If the index is already deselected, ignore it.
883 if (option && option->Selected()) {
884 if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
885 // To notify the frame if anything gets changed, don't
886 // flush, if the frame doesn't exist we don't need to
887 // create it just to tell it about this change.
888 selectFrame = GetSelectFrame();
889 weakSelectFrame = do_QueryFrame(selectFrame);
891 didGetFrame = true;
894 OnOptionSelected(selectFrame, optIndex, false, true,
895 aOptionsMask.contains(OptionFlag::Notify));
896 optionsDeselected = true;
898 // Only need to deselect one option if not multiple
899 if (!isMultiple) {
900 break;
906 } else {
907 // If we're deselecting, loop through all selected items and deselect
908 // any that are in the specified range.
909 for (int32_t optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) {
910 HTMLOptionElement* option = Item(optIndex);
911 if (!aOptionsMask.contains(OptionFlag::SetDisabled) &&
912 IsOptionDisabled(option)) {
913 continue;
916 // If the index is already selected, ignore it.
917 if (option && option->Selected()) {
918 if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
919 // To notify the frame if anything gets changed, don't
920 // flush, if the frame doesn't exist we don't need to
921 // create it just to tell it about this change.
922 selectFrame = GetSelectFrame();
923 weakSelectFrame = do_QueryFrame(selectFrame);
925 didGetFrame = true;
928 OnOptionSelected(selectFrame, optIndex, false, true,
929 aOptionsMask.contains(OptionFlag::Notify));
930 optionsDeselected = true;
935 // Make sure something is selected unless we were set to -1 (none)
936 if (optionsDeselected && aStartIndex != -1 &&
937 !aOptionsMask.contains(OptionFlag::NoReselect)) {
938 optionsSelected =
939 CheckSelectSomething(aOptionsMask.contains(OptionFlag::Notify)) ||
940 optionsSelected;
943 // Let the caller know whether anything was changed
944 return optionsSelected || optionsDeselected;
947 NS_IMETHODIMP
948 HTMLSelectElement::IsOptionDisabled(int32_t aIndex, bool* aIsDisabled) {
949 *aIsDisabled = false;
950 RefPtr<HTMLOptionElement> option = Item(aIndex);
951 NS_ENSURE_TRUE(option, NS_ERROR_FAILURE);
953 *aIsDisabled = IsOptionDisabled(option);
954 return NS_OK;
957 bool HTMLSelectElement::IsOptionDisabled(HTMLOptionElement* aOption) const {
958 MOZ_ASSERT(aOption);
959 if (aOption->Disabled()) {
960 return true;
963 // Check for disabled optgroups
964 // If there are no artifacts, there are no optgroups
965 if (mNonOptionChildren) {
966 for (nsCOMPtr<Element> node =
967 static_cast<nsINode*>(aOption)->GetParentElement();
968 node; node = node->GetParentElement()) {
969 // If we reached the select element, we're done
970 if (node->IsHTMLElement(nsGkAtoms::select)) {
971 return false;
974 RefPtr<HTMLOptGroupElement> optGroupElement =
975 HTMLOptGroupElement::FromNode(node);
977 if (!optGroupElement) {
978 // If you put something else between you and the optgroup, you're a
979 // moron and you deserve not to have optgroup disabling work.
980 return false;
983 if (optGroupElement->Disabled()) {
984 return true;
989 return false;
992 void HTMLSelectElement::GetValue(DOMString& aValue) const {
993 int32_t selectedIndex = SelectedIndex();
994 if (selectedIndex < 0) {
995 return;
998 RefPtr<HTMLOptionElement> option = Item(static_cast<uint32_t>(selectedIndex));
1000 if (!option) {
1001 return;
1004 option->GetValue(aValue);
1007 void HTMLSelectElement::SetValue(const nsAString& aValue) {
1008 uint32_t length = Length();
1010 for (uint32_t i = 0; i < length; i++) {
1011 RefPtr<HTMLOptionElement> option = Item(i);
1012 if (!option) {
1013 continue;
1016 nsAutoString optionVal;
1017 option->GetValue(optionVal);
1018 if (optionVal.Equals(aValue)) {
1019 SetSelectedIndexInternal(int32_t(i), true);
1020 return;
1023 // No matching option was found.
1024 SetSelectedIndexInternal(-1, true);
1027 int32_t HTMLSelectElement::TabIndexDefault() { return 0; }
1029 bool HTMLSelectElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
1030 int32_t* aTabIndex) {
1031 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
1032 aWithMouse, aIsFocusable, aTabIndex)) {
1033 return true;
1036 *aIsFocusable = !IsDisabled();
1038 return false;
1041 bool HTMLSelectElement::CheckSelectSomething(bool aNotify) {
1042 if (mIsDoneAddingChildren) {
1043 if (mSelectedIndex < 0 && IsCombobox()) {
1044 return SelectSomething(aNotify);
1047 return false;
1050 bool HTMLSelectElement::SelectSomething(bool aNotify) {
1051 // If we're not done building the select, don't play with this yet.
1052 if (!mIsDoneAddingChildren) {
1053 return false;
1056 uint32_t count = Length();
1057 for (uint32_t i = 0; i < count; i++) {
1058 bool disabled;
1059 nsresult rv = IsOptionDisabled(i, &disabled);
1061 if (NS_FAILED(rv) || !disabled) {
1062 SetSelectedIndexInternal(i, aNotify);
1064 UpdateValueMissingValidityState();
1065 UpdateValidityElementStates(aNotify);
1067 return true;
1071 return false;
1074 nsresult HTMLSelectElement::BindToTree(BindContext& aContext,
1075 nsINode& aParent) {
1076 nsresult rv =
1077 nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
1078 NS_ENSURE_SUCCESS(rv, rv);
1080 // If there is a disabled fieldset in the parent chain, the element is now
1081 // barred from constraint validation.
1082 // XXXbz is this still needed now that fieldset changes always call
1083 // FieldSetDisabledChanged?
1084 UpdateBarredFromConstraintValidation();
1086 // And now make sure our state is up to date
1087 UpdateValidityElementStates(false);
1089 return rv;
1092 void HTMLSelectElement::UnbindFromTree(UnbindContext& aContext) {
1093 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext);
1095 // We might be no longer disabled because our parent chain changed.
1096 // XXXbz is this still needed now that fieldset changes always call
1097 // FieldSetDisabledChanged?
1098 UpdateBarredFromConstraintValidation();
1100 // And now make sure our state is up to date
1101 UpdateValidityElementStates(false);
1104 void HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1105 const nsAttrValue* aValue, bool aNotify) {
1106 if (aNameSpaceID == kNameSpaceID_None) {
1107 if (aName == nsGkAtoms::disabled) {
1108 if (aNotify) {
1109 mDisabledChanged = true;
1111 } else if (aName == nsGkAtoms::multiple) {
1112 if (!aValue && aNotify && mSelectedIndex >= 0) {
1113 // We're changing from being a multi-select to a single-select.
1114 // Make sure we only have one option selected before we do that.
1115 // Note that this needs to come before we really unset the attr,
1116 // since SetOptionsSelectedByIndex does some bail-out type
1117 // optimization for cases when the select is not multiple that
1118 // would lead to only a single option getting deselected.
1119 SetSelectedIndexInternal(mSelectedIndex, aNotify);
1124 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
1125 aNameSpaceID, aName, aValue, aNotify);
1128 void HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1129 const nsAttrValue* aValue,
1130 const nsAttrValue* aOldValue,
1131 nsIPrincipal* aSubjectPrincipal,
1132 bool aNotify) {
1133 if (aNameSpaceID == kNameSpaceID_None) {
1134 if (aName == nsGkAtoms::disabled) {
1135 // This *has* to be called *before* validity state check because
1136 // UpdateBarredFromConstraintValidation and
1137 // UpdateValueMissingValidityState depend on our disabled state.
1138 UpdateDisabledState(aNotify);
1140 UpdateValueMissingValidityState();
1141 UpdateBarredFromConstraintValidation();
1142 UpdateValidityElementStates(aNotify);
1143 } else if (aName == nsGkAtoms::required) {
1144 // This *has* to be called *before* UpdateValueMissingValidityState
1145 // because UpdateValueMissingValidityState depends on our required
1146 // state.
1147 UpdateRequiredState(!!aValue, aNotify);
1148 UpdateValueMissingValidityState();
1149 UpdateValidityElementStates(aNotify);
1150 } else if (aName == nsGkAtoms::autocomplete) {
1151 // Clear the cached @autocomplete attribute and autocompleteInfo state.
1152 mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
1153 mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
1154 } else if (aName == nsGkAtoms::multiple) {
1155 if (!aValue && aNotify) {
1156 // We might have become a combobox; make sure _something_ gets
1157 // selected in that case
1158 CheckSelectSomething(aNotify);
1163 return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
1164 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
1167 void HTMLSelectElement::DoneAddingChildren(bool aHaveNotified) {
1168 mIsDoneAddingChildren = true;
1170 nsISelectControlFrame* selectFrame = GetSelectFrame();
1172 // If we foolishly tried to restore before we were done adding
1173 // content, restore the rest of the options proper-like
1174 if (mRestoreState) {
1175 RestoreStateTo(*mRestoreState);
1176 mRestoreState = nullptr;
1179 // Notify the frame
1180 if (selectFrame) {
1181 selectFrame->DoneAddingChildren(true);
1184 if (!mInhibitStateRestoration) {
1185 GenerateStateKey();
1186 RestoreFormControlState();
1189 // Now that we're done, select something (if it's a single select something
1190 // must be selected)
1191 if (!CheckSelectSomething(false)) {
1192 // If an option has @selected set, it will be selected during parsing but
1193 // with an empty value. We have to make sure the select element updates it's
1194 // validity state to take this into account.
1195 UpdateValueMissingValidityState();
1197 // And now make sure we update our content state too
1198 UpdateValidityElementStates(aHaveNotified);
1201 mDefaultSelectionSet = true;
1204 bool HTMLSelectElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
1205 const nsAString& aValue,
1206 nsIPrincipal* aMaybeScriptedPrincipal,
1207 nsAttrValue& aResult) {
1208 if (kNameSpaceID_None == aNamespaceID) {
1209 if (aAttribute == nsGkAtoms::size) {
1210 return aResult.ParsePositiveIntValue(aValue);
1212 if (aAttribute == nsGkAtoms::autocomplete) {
1213 aResult.ParseAtomArray(aValue);
1214 return true;
1217 return nsGenericHTMLFormControlElementWithState::ParseAttribute(
1218 aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
1221 void HTMLSelectElement::MapAttributesIntoRule(
1222 MappedDeclarationsBuilder& aBuilder) {
1223 nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto(
1224 aBuilder);
1225 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder);
1228 nsChangeHint HTMLSelectElement::GetAttributeChangeHint(const nsAtom* aAttribute,
1229 int32_t aModType) const {
1230 nsChangeHint retval =
1231 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
1232 aAttribute, aModType);
1233 if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) {
1234 retval |= nsChangeHint_ReconstructFrame;
1236 return retval;
1239 NS_IMETHODIMP_(bool)
1240 HTMLSelectElement::IsAttributeMapped(const nsAtom* aAttribute) const {
1241 static const MappedAttributeEntry* const map[] = {sCommonAttributeMap,
1242 sImageAlignAttributeMap};
1244 return FindAttributeDependence(aAttribute, map);
1247 nsMapRuleToAttributesFunc HTMLSelectElement::GetAttributeMappingFunction()
1248 const {
1249 return &MapAttributesIntoRule;
1252 bool HTMLSelectElement::IsDisabledForEvents(WidgetEvent* aEvent) {
1253 nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
1254 nsIFrame* formFrame = nullptr;
1255 if (formControlFrame) {
1256 formFrame = do_QueryFrame(formControlFrame);
1258 return IsElementDisabledForEvents(aEvent, formFrame);
1261 void HTMLSelectElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
1262 aVisitor.mCanHandle = false;
1263 if (IsDisabledForEvents(aVisitor.mEvent)) {
1264 return;
1267 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor);
1270 void HTMLSelectElement::UpdateValidityElementStates(bool aNotify) {
1271 AutoStateChangeNotifier notifier(*this, aNotify);
1272 RemoveStatesSilently(ElementState::VALIDITY_STATES);
1273 if (!IsCandidateForConstraintValidation()) {
1274 return;
1277 ElementState state;
1278 if (IsValid()) {
1279 state |= ElementState::VALID;
1280 if (mUserInteracted) {
1281 state |= ElementState::USER_VALID;
1283 } else {
1284 state |= ElementState::INVALID;
1285 if (mUserInteracted) {
1286 state |= ElementState::USER_INVALID;
1290 AddStatesSilently(state);
1293 void HTMLSelectElement::SaveState() {
1294 PresState* presState = GetPrimaryPresState();
1295 if (!presState) {
1296 return;
1299 SelectContentData state;
1301 uint32_t len = Length();
1303 for (uint32_t optIndex = 0; optIndex < len; optIndex++) {
1304 HTMLOptionElement* option = Item(optIndex);
1305 if (option && option->Selected()) {
1306 nsAutoString value;
1307 option->GetValue(value);
1308 if (value.IsEmpty()) {
1309 state.indices().AppendElement(optIndex);
1310 } else {
1311 state.values().AppendElement(std::move(value));
1316 presState->contentData() = std::move(state);
1318 if (mDisabledChanged) {
1319 // We do not want to save the real disabled state but the disabled
1320 // attribute.
1321 presState->disabled() = HasAttr(nsGkAtoms::disabled);
1322 presState->disabledSet() = true;
1326 bool HTMLSelectElement::RestoreState(PresState* aState) {
1327 // Get the presentation state object to retrieve our stuff out of.
1328 const PresContentData& state = aState->contentData();
1329 if (state.type() == PresContentData::TSelectContentData) {
1330 RestoreStateTo(state.get_SelectContentData());
1332 // Don't flush, if the frame doesn't exist yet it doesn't care if
1333 // we're reset or not.
1334 DispatchContentReset();
1337 if (aState->disabledSet() && !aState->disabled()) {
1338 SetDisabled(false, IgnoreErrors());
1341 return false;
1344 void HTMLSelectElement::RestoreStateTo(const SelectContentData& aNewSelected) {
1345 if (!mIsDoneAddingChildren) {
1346 // Make a copy of the state for us to restore from in the future.
1347 mRestoreState = MakeUnique<SelectContentData>(aNewSelected);
1348 return;
1351 uint32_t len = Length();
1352 OptionFlags mask{OptionFlag::IsSelected, OptionFlag::ClearAll,
1353 OptionFlag::SetDisabled, OptionFlag::Notify};
1355 // First clear all
1356 SetOptionsSelectedByIndex(-1, -1, mask);
1358 // Select by index.
1359 for (uint32_t idx : aNewSelected.indices()) {
1360 if (idx < len) {
1361 SetOptionsSelectedByIndex(idx, idx,
1362 {OptionFlag::IsSelected,
1363 OptionFlag::SetDisabled, OptionFlag::Notify});
1367 // Select by value.
1368 for (uint32_t i = 0; i < len; ++i) {
1369 HTMLOptionElement* option = Item(i);
1370 if (option) {
1371 nsAutoString value;
1372 option->GetValue(value);
1373 if (aNewSelected.values().Contains(value)) {
1374 SetOptionsSelectedByIndex(
1375 i, i,
1376 {OptionFlag::IsSelected, OptionFlag::SetDisabled,
1377 OptionFlag::Notify});
1383 // nsIFormControl
1385 NS_IMETHODIMP
1386 HTMLSelectElement::Reset() {
1387 uint32_t numSelected = 0;
1390 // Cycle through the options array and reset the options
1392 uint32_t numOptions = Length();
1394 for (uint32_t i = 0; i < numOptions; i++) {
1395 RefPtr<HTMLOptionElement> option = Item(i);
1396 if (option) {
1398 // Reset the option to its default value
1401 OptionFlags mask = {OptionFlag::SetDisabled, OptionFlag::Notify,
1402 OptionFlag::NoReselect};
1403 if (option->DefaultSelected()) {
1404 mask += OptionFlag::IsSelected;
1405 numSelected++;
1408 SetOptionsSelectedByIndex(i, i, mask);
1409 option->SetSelectedChanged(false);
1414 // If nothing was selected and it's not multiple, select something
1416 if (numSelected == 0 && IsCombobox()) {
1417 SelectSomething(true);
1420 OnSelectionChanged();
1421 SetUserInteracted(false);
1423 // Let the frame know we were reset
1425 // Don't flush, if there's no frame yet it won't care about us being
1426 // reset even if we forced it to be created now.
1428 DispatchContentReset();
1430 return NS_OK;
1433 NS_IMETHODIMP
1434 HTMLSelectElement::SubmitNamesValues(FormData* aFormData) {
1436 // Get the name (if no name, no submit)
1438 nsAutoString name;
1439 GetAttr(nsGkAtoms::name, name);
1440 if (name.IsEmpty()) {
1441 return NS_OK;
1445 // Submit
1447 uint32_t len = Length();
1449 for (uint32_t optIndex = 0; optIndex < len; optIndex++) {
1450 HTMLOptionElement* option = Item(optIndex);
1452 // Don't send disabled options
1453 if (!option || IsOptionDisabled(option)) {
1454 continue;
1457 if (!option->Selected()) {
1458 continue;
1461 nsString value;
1462 option->GetValue(value);
1464 aFormData->AddNameValuePair(name, value);
1467 return NS_OK;
1470 void HTMLSelectElement::DispatchContentReset() {
1471 if (nsIFormControlFrame* formControlFrame = GetFormControlFrame(false)) {
1472 if (nsListControlFrame* listFrame = do_QueryFrame(formControlFrame)) {
1473 listFrame->OnContentReset();
1478 static void AddOptions(nsIContent* aRoot, HTMLOptionsCollection* aArray) {
1479 for (nsIContent* child = aRoot->GetFirstChild(); child;
1480 child = child->GetNextSibling()) {
1481 HTMLOptionElement* opt = HTMLOptionElement::FromNode(child);
1482 if (opt) {
1483 aArray->AppendOption(opt);
1484 } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) {
1485 for (nsIContent* grandchild = child->GetFirstChild(); grandchild;
1486 grandchild = grandchild->GetNextSibling()) {
1487 opt = HTMLOptionElement::FromNode(grandchild);
1488 if (opt) {
1489 aArray->AppendOption(opt);
1496 void HTMLSelectElement::RebuildOptionsArray(bool aNotify) {
1497 mOptions->Clear();
1498 AddOptions(this, mOptions);
1499 FindSelectedIndex(0, aNotify);
1502 bool HTMLSelectElement::IsValueMissing() const {
1503 if (!Required()) {
1504 return false;
1507 uint32_t length = Length();
1509 for (uint32_t i = 0; i < length; ++i) {
1510 RefPtr<HTMLOptionElement> option = Item(i);
1511 // Check for a placeholder label option, don't count it as a valid value.
1512 if (i == 0 && !Multiple() && Size() <= 1 && option->GetParent() == this) {
1513 nsAutoString value;
1514 option->GetValue(value);
1515 if (value.IsEmpty()) {
1516 continue;
1520 if (!option->Selected()) {
1521 continue;
1524 return false;
1527 return true;
1530 void HTMLSelectElement::UpdateValueMissingValidityState() {
1531 SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
1534 nsresult HTMLSelectElement::GetValidationMessage(nsAString& aValidationMessage,
1535 ValidityStateType aType) {
1536 switch (aType) {
1537 case VALIDITY_STATE_VALUE_MISSING: {
1538 nsAutoString message;
1539 nsresult rv = nsContentUtils::GetMaybeLocalizedString(
1540 nsContentUtils::eDOM_PROPERTIES, "FormValidationSelectMissing",
1541 OwnerDoc(), message);
1542 aValidationMessage = message;
1543 return rv;
1545 default: {
1546 return ConstraintValidation::GetValidationMessage(aValidationMessage,
1547 aType);
1552 #ifdef DEBUG
1554 void HTMLSelectElement::VerifyOptionsArray() {
1555 int32_t index = 0;
1556 for (nsIContent* child = nsINode::GetFirstChild(); child;
1557 child = child->GetNextSibling()) {
1558 HTMLOptionElement* opt = HTMLOptionElement::FromNode(child);
1559 if (opt) {
1560 NS_ASSERTION(opt == mOptions->ItemAsOption(index++),
1561 "Options collection broken");
1562 } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) {
1563 for (nsIContent* grandchild = child->GetFirstChild(); grandchild;
1564 grandchild = grandchild->GetNextSibling()) {
1565 opt = HTMLOptionElement::FromNode(grandchild);
1566 if (opt) {
1567 NS_ASSERTION(opt == mOptions->ItemAsOption(index++),
1568 "Options collection broken");
1575 #endif
1577 void HTMLSelectElement::UpdateBarredFromConstraintValidation() {
1578 SetBarredFromConstraintValidation(
1579 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled());
1582 void HTMLSelectElement::FieldSetDisabledChanged(bool aNotify) {
1583 // This *has* to be called before UpdateBarredFromConstraintValidation and
1584 // UpdateValueMissingValidityState because these two functions depend on our
1585 // disabled state.
1586 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify);
1588 UpdateValueMissingValidityState();
1589 UpdateBarredFromConstraintValidation();
1590 UpdateValidityElementStates(aNotify);
1593 void HTMLSelectElement::OnSelectionChanged() {
1594 if (!mDefaultSelectionSet) {
1595 return;
1597 UpdateSelectedOptions();
1600 void HTMLSelectElement::UpdateSelectedOptions() {
1601 if (mSelectedOptions) {
1602 mSelectedOptions->SetDirty();
1606 void HTMLSelectElement::SetUserInteracted(bool aInteracted) {
1607 if (mUserInteracted == aInteracted) {
1608 return;
1610 mUserInteracted = aInteracted;
1611 UpdateValidityElementStates(true);
1614 void HTMLSelectElement::SetPreviewValue(const nsAString& aValue) {
1615 mPreviewValue = aValue;
1616 nsContentUtils::RemoveNewlines(mPreviewValue);
1617 nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
1618 nsComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame);
1619 if (comboFrame) {
1620 comboFrame->RedisplaySelectedText();
1624 void HTMLSelectElement::UserFinishedInteracting(bool aChanged) {
1625 SetUserInteracted(true);
1626 if (!aChanged) {
1627 return;
1630 // Dispatch the input event.
1631 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
1632 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1633 "Failed to dispatch input event");
1635 // Dispatch the change event.
1636 nsContentUtils::DispatchTrustedEvent(OwnerDoc(), this, u"change"_ns,
1637 CanBubble::eYes, Cancelable::eNo);
1640 JSObject* HTMLSelectElement::WrapNode(JSContext* aCx,
1641 JS::Handle<JSObject*> aGivenProto) {
1642 return HTMLSelectElement_Binding::Wrap(aCx, this, aGivenProto);
1645 } // namespace mozilla::dom