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"
27 #include "nsGkAtoms.h"
28 #include "nsComboboxControlFrame.h"
29 #include "mozilla/dom/Document.h"
30 #include "nsIFormControlFrame.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
,
52 uint32_t aIndex
, bool aNotify
)
53 : mSelect(HTMLSelectElement::FromNodeOrNull(aSelect
)),
54 mTopLevelMutation(false),
58 mInitialSelectedOption
= mSelect
->Item(mSelect
->SelectedIndex());
59 mTopLevelMutation
= !mSelect
->mMutating
;
60 if (mTopLevelMutation
) {
61 mSelect
->mMutating
= true;
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
);
71 rv
= mSelect
->WillAddOptions(aKid
, aParent
, aIndex
, mNotify
);
73 rv
= mSelect
->WillRemoveOptions(aParent
, aIndex
, mNotify
);
75 mNeedsRebuild
= NS_FAILED(rv
);
79 SafeOptionListMutation::~SafeOptionListMutation() {
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
);
98 mSelect
->VerifyOptionsArray();
103 //----------------------------------------------------------------------
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),
121 mInhibitStateRestoration(!!(aFromParser
& FROM_PARSER_FRAGMENT
)),
122 mUserInteracted(false),
123 mDefaultSelectionSet(false),
124 mIsOpenInParentProcess(false),
125 mNonOptionChildren(0),
128 SetHasWeirdParserInsertionMode();
130 // DoneAddingChildren() will be called later if it's from the parser,
133 // Set up our default state: enabled, optional, and valid.
134 AddStatesSilently(ElementState::ENABLED
| ElementState::OPTIONAL_
|
135 ElementState::VALID
);
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"
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 "
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
);
200 return aRv
.ThrowNotSupportedError("This select isn't being rendered.");
203 // Step 5. Show the picker, if applicable, for this.
204 #if !defined(ANDROID)
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
,
232 const uint32_t index
=
233 aBeforeThis
? *ComputeIndexOf(aBeforeThis
) : GetChildCount();
234 SafeOptionListMutation
safeMutation(this, this, aKid
, index
, aNotify
);
235 nsGenericHTMLFormControlElementWithState::InsertChildBefore(aKid
, aBeforeThis
,
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
,
250 int32_t aDepth
, bool aNotify
) {
251 MOZ_ASSERT(aDepth
== 0 || aDepth
== 1);
252 int32_t insertIndex
= aListIndex
;
254 HTMLOptionElement
* optElement
= HTMLOptionElement::FromNode(aOptions
);
256 mOptions
->InsertOptionAt(optElement
, 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
)) {
267 for (nsIContent
* child
= aOptions
->GetFirstChild(); child
;
268 child
= child
->GetNextSibling()) {
269 optElement
= HTMLOptionElement::FromNode(child
);
271 mOptions
->InsertOptionAt(optElement
, 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
);
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
319 OnOptionSelected(selectFrame
, i
, true, false, aNotify
);
323 CheckSelectSomething(aNotify
);
327 nsresult
HTMLSelectElement::RemoveOptionsFromList(nsIContent
* aOptions
,
331 MOZ_ASSERT(aDepth
== 0 || aDepth
== 1);
332 int32_t numRemoved
= 0;
334 HTMLOptionElement
* optElement
= HTMLOptionElement::FromNode(aOptions
);
336 if (mOptions
->ItemAsOption(aListIndex
) != optElement
) {
337 NS_ERROR("wrong option at index");
338 return NS_ERROR_UNEXPECTED
;
340 mOptions
->RemoveOptionAt(aListIndex
);
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
)) {
350 for (nsIContent
* child
= aOptions
->GetFirstChild(); child
;
351 child
= child
->GetNextSibling()) {
352 optElement
= HTMLOptionElement::FromNode(child
);
354 if (mOptions
->ItemAsOption(aListIndex
) != optElement
) {
355 NS_ERROR("wrong option at index");
356 return NS_ERROR_UNEXPECTED
;
358 mOptions
->RemoveOptionAt(aListIndex
);
363 } // else don't check for an optgroup; we want to ignore nested optgroups
366 // Tell the widget we removed the options
367 nsISelectControlFrame
* selectFrame
= GetSelectFrame();
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.
383 OnSelectionChanged();
385 FindSelectedIndex(aListIndex
, aNotify
);
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
397 if (!CheckSelectSomething(aNotify
) && mSelectedIndex
== -1) {
398 // Update the validity state in case of we've just removed the last
400 UpdateValueMissingValidityState();
401 UpdateValidityElementStates(aNotify
);
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.
414 HTMLSelectElement::WillAddOptions(nsIContent
* aOptions
, nsIContent
* aParent
,
415 int32_t aContentIndex
, bool aNotify
) {
416 if (this != aParent
&& this != aParent
->GetParent()) {
419 int32_t level
= aParent
== this ? 0 : 1;
421 // Get the index where the options will be inserted
423 if (!mNonOptionChildren
) {
424 // If there are no artifacts, aContentIndex == ind
427 // If there are artifacts, we have to get the index of the option the
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
);
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
439 nsIContent
* currentKid
= aParent
->GetChildAt_Deprecated(aContentIndex
);
440 NS_ASSERTION(currentKid
, "Child not found!");
442 ind
= GetOptionIndexAt(currentKid
);
449 InsertOptionsIntoList(aOptions
, ind
, level
, aNotify
);
454 HTMLSelectElement::WillRemoveOptions(nsIContent
* aParent
, int32_t aContentIndex
,
456 if (this != aParent
&& this != aParent
->GetParent()) {
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
);
465 if (!mNonOptionChildren
) {
466 // If there are no artifacts, aContentIndex == ind
469 // If there are artifacts, we have to get the index of the option the
471 ind
= GetFirstOptionIndex(currentKid
);
474 nsresult rv
= RemoveOptionsFromList(currentKid
, ind
, level
, aNotify
);
475 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
487 retval
= GetOptionIndexAfter(aOptions
);
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
497 // - If it's not there, search for the first option after the parent.
498 if (aOptions
== this) {
504 nsCOMPtr
<nsIContent
> parent
= aOptions
->GetParent();
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
);
513 retval
= GetOptionIndexAfter(parent
);
520 int32_t HTMLSelectElement::GetFirstOptionIndex(nsIContent
* aOptions
) {
521 int32_t listIndex
= -1;
522 HTMLOptionElement
* optElement
= HTMLOptionElement::FromNode(aOptions
);
524 mOptions
->GetOptionIndex(optElement
, 0, true, &listIndex
);
528 listIndex
= GetFirstChildOptionIndex(aOptions
, 0, aOptions
->GetChildCount());
533 int32_t HTMLSelectElement::GetFirstChildOptionIndex(nsIContent
* aOptions
,
538 for (int32_t i
= aStartIndex
; i
< aEndIndex
; ++i
) {
539 retval
= GetFirstOptionIndex(aOptions
->GetChildAt_Deprecated(i
));
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
);
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
);
574 Add(element
, aBefore
.Value().GetAsLong(), aRv
);
578 void HTMLSelectElement::Add(nsGenericHTMLElement
& aElement
,
579 nsGenericHTMLElement
* aBefore
,
580 ErrorResult
& aError
) {
582 Element::AppendChild(aElement
, aError
);
586 // Just in case we're not the parent, get the parent of the reference
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
592 aError
.Throw(NS_ERROR_DOM_NOT_FOUND_ERR
);
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 {
607 nsCOMPtr
<nsINode
> option
= Item(static_cast<uint32_t>(aIndex
));
615 void HTMLSelectElement::GetType(nsAString
& aType
) {
616 if (HasAttr(nsGkAtoms::multiple
)) {
617 aType
.AssignLiteral("select-multiple");
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
) {
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
});
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
);
659 if (i
+ 1 < aLength
) {
660 node
= node
->CloneNode(true, aRv
);
671 bool HTMLSelectElement::MatchSelectedOptions(Element
* aElement
,
672 int32_t /* unused */,
673 nsAtom
* /* 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
};
692 mask
+= OptionFlag::Notify
;
695 SetOptionsSelectedByIndex(aIndex
, aIndex
, mask
);
697 nsISelectControlFrame
* selectFrame
= GetSelectFrame();
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
,
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
));
726 option
->SetSelectedInternal(aSelected
, aNotify
);
730 // Let the frame know too
732 aSelectFrame
->OnOptionSelected(aIndex
, aSelected
);
735 UpdateSelectedOptions();
736 UpdateValueMissingValidityState();
737 UpdateValidityElementStates(aNotify
);
740 void HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex
, bool aNotify
) {
742 uint32_t len
= Length();
743 for (int32_t i
= aStartIndex
; i
< int32_t(len
); i
++) {
744 if (IsOptionSelectedByIndex(i
)) {
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
,
778 OptionFlags aOptionsMask
) {
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'));
784 // Don't bother if the select is disabled
785 if (!aOptionsMask
.contains(OptionFlag::SetDisabled
) && IsDisabled()) {
789 // Don't bother if there are no options
790 uint32_t numItems
= Length();
795 // First, find out whether multiple items can be selected
796 bool isMultiple
= Multiple();
798 // These variables tell us whether any options were selected
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
) {
815 // Only select the first value if it's not multiple
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
)) {
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
860 selectFrame
= GetSelectFrame();
861 weakSelectFrame
= do_QueryFrame(selectFrame
);
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
);
894 OnOptionSelected(selectFrame
, optIndex
, false, true,
895 aOptionsMask
.contains(OptionFlag::Notify
));
896 optionsDeselected
= true;
898 // Only need to deselect one option if not multiple
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
)) {
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
);
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
)) {
939 CheckSelectSomething(aOptionsMask
.contains(OptionFlag::Notify
)) ||
943 // Let the caller know whether anything was changed
944 return optionsSelected
|| optionsDeselected
;
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
);
957 bool HTMLSelectElement::IsOptionDisabled(HTMLOptionElement
* aOption
) const {
959 if (aOption
->Disabled()) {
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
)) {
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.
983 if (optGroupElement
->Disabled()) {
992 void HTMLSelectElement::GetValue(DOMString
& aValue
) const {
993 int32_t selectedIndex
= SelectedIndex();
994 if (selectedIndex
< 0) {
998 RefPtr
<HTMLOptionElement
> option
= Item(static_cast<uint32_t>(selectedIndex
));
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
);
1016 nsAutoString optionVal
;
1017 option
->GetValue(optionVal
);
1018 if (optionVal
.Equals(aValue
)) {
1019 SetSelectedIndexInternal(int32_t(i
), true);
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
)) {
1036 *aIsFocusable
= !IsDisabled();
1041 bool HTMLSelectElement::CheckSelectSomething(bool aNotify
) {
1042 if (mIsDoneAddingChildren
) {
1043 if (mSelectedIndex
< 0 && IsCombobox()) {
1044 return SelectSomething(aNotify
);
1050 bool HTMLSelectElement::SelectSomething(bool aNotify
) {
1051 // If we're not done building the select, don't play with this yet.
1052 if (!mIsDoneAddingChildren
) {
1056 uint32_t count
= Length();
1057 for (uint32_t i
= 0; i
< count
; i
++) {
1059 nsresult rv
= IsOptionDisabled(i
, &disabled
);
1061 if (NS_FAILED(rv
) || !disabled
) {
1062 SetSelectedIndexInternal(i
, aNotify
);
1064 UpdateValueMissingValidityState();
1065 UpdateValidityElementStates(aNotify
);
1074 nsresult
HTMLSelectElement::BindToTree(BindContext
& aContext
,
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);
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
) {
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
,
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
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;
1181 selectFrame
->DoneAddingChildren(true);
1184 if (!mInhibitStateRestoration
) {
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
);
1217 return nsGenericHTMLFormControlElementWithState::ParseAttribute(
1218 aNamespaceID
, aAttribute
, aValue
, aMaybeScriptedPrincipal
, aResult
);
1221 void HTMLSelectElement::MapAttributesIntoRule(
1222 MappedDeclarationsBuilder
& aBuilder
) {
1223 nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto(
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
;
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()
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
)) {
1267 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor
);
1270 void HTMLSelectElement::UpdateValidityElementStates(bool aNotify
) {
1271 AutoStateChangeNotifier
notifier(*this, aNotify
);
1272 RemoveStatesSilently(ElementState::VALIDITY_STATES
);
1273 if (!IsCandidateForConstraintValidation()) {
1279 state
|= ElementState::VALID
;
1280 if (mUserInteracted
) {
1281 state
|= ElementState::USER_VALID
;
1284 state
|= ElementState::INVALID
;
1285 if (mUserInteracted
) {
1286 state
|= ElementState::USER_INVALID
;
1290 AddStatesSilently(state
);
1293 void HTMLSelectElement::SaveState() {
1294 PresState
* presState
= GetPrimaryPresState();
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()) {
1307 option
->GetValue(value
);
1308 if (value
.IsEmpty()) {
1309 state
.indices().AppendElement(optIndex
);
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
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());
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
);
1351 uint32_t len
= Length();
1352 OptionFlags mask
{OptionFlag::IsSelected
, OptionFlag::ClearAll
,
1353 OptionFlag::SetDisabled
, OptionFlag::Notify
};
1356 SetOptionsSelectedByIndex(-1, -1, mask
);
1359 for (uint32_t idx
: aNewSelected
.indices()) {
1361 SetOptionsSelectedByIndex(idx
, idx
,
1362 {OptionFlag::IsSelected
,
1363 OptionFlag::SetDisabled
, OptionFlag::Notify
});
1368 for (uint32_t i
= 0; i
< len
; ++i
) {
1369 HTMLOptionElement
* option
= Item(i
);
1372 option
->GetValue(value
);
1373 if (aNewSelected
.values().Contains(value
)) {
1374 SetOptionsSelectedByIndex(
1376 {OptionFlag::IsSelected
, OptionFlag::SetDisabled
,
1377 OptionFlag::Notify
});
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
);
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
;
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();
1434 HTMLSelectElement::SubmitNamesValues(FormData
* aFormData
) {
1436 // Get the name (if no name, no submit)
1439 GetAttr(nsGkAtoms::name
, name
);
1440 if (name
.IsEmpty()) {
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
)) {
1457 if (!option
->Selected()) {
1462 option
->GetValue(value
);
1464 aFormData
->AddNameValuePair(name
, value
);
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
);
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
);
1489 aArray
->AppendOption(opt
);
1496 void HTMLSelectElement::RebuildOptionsArray(bool aNotify
) {
1498 AddOptions(this, mOptions
);
1499 FindSelectedIndex(0, aNotify
);
1502 bool HTMLSelectElement::IsValueMissing() const {
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) {
1514 option
->GetValue(value
);
1515 if (value
.IsEmpty()) {
1520 if (!option
->Selected()) {
1530 void HTMLSelectElement::UpdateValueMissingValidityState() {
1531 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, IsValueMissing());
1534 nsresult
HTMLSelectElement::GetValidationMessage(nsAString
& aValidationMessage
,
1535 ValidityStateType 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
;
1546 return ConstraintValidation::GetValidationMessage(aValidationMessage
,
1554 void HTMLSelectElement::VerifyOptionsArray() {
1556 for (nsIContent
* child
= nsINode::GetFirstChild(); child
;
1557 child
= child
->GetNextSibling()) {
1558 HTMLOptionElement
* opt
= HTMLOptionElement::FromNode(child
);
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
);
1567 NS_ASSERTION(opt
== mOptions
->ItemAsOption(index
++),
1568 "Options collection broken");
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
1586 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify
);
1588 UpdateValueMissingValidityState();
1589 UpdateBarredFromConstraintValidation();
1590 UpdateValidityElementStates(aNotify
);
1593 void HTMLSelectElement::OnSelectionChanged() {
1594 if (!mDefaultSelectionSet
) {
1597 UpdateSelectedOptions();
1600 void HTMLSelectElement::UpdateSelectedOptions() {
1601 if (mSelectedOptions
) {
1602 mSelectedOptions
->SetDirty();
1606 void HTMLSelectElement::SetUserInteracted(bool aInteracted
) {
1607 if (mUserInteracted
== aInteracted
) {
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
);
1620 comboFrame
->RedisplaySelectedText();
1624 void HTMLSelectElement::UserFinishedInteracting(bool aChanged
) {
1625 SetUserInteracted(true);
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