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/HTMLOptionElement.h"
9 #include "HTMLOptGroupElement.h"
10 #include "mozilla/dom/HTMLOptionElementBinding.h"
11 #include "mozilla/dom/HTMLSelectElement.h"
12 #include "nsGkAtoms.h"
13 #include "nsStyleConsts.h"
14 #include "nsIFormControl.h"
15 #include "nsISelectControlFrame.h"
17 // Notify/query select frame for selected state
18 #include "nsIFormControlFrame.h"
19 #include "mozilla/dom/Document.h"
20 #include "nsNodeInfoManager.h"
22 #include "nsContentCreatorFunctions.h"
23 #include "mozAutoDocUpdate.h"
24 #include "nsTextNode.h"
27 * Implementation of <option>
30 NS_IMPL_NS_NEW_HTML_ELEMENT(Option
)
32 namespace mozilla::dom
{
34 HTMLOptionElement::HTMLOptionElement(
35 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
36 : nsGenericHTMLElement(std::move(aNodeInfo
)) {
37 // We start off enabled
38 AddStatesSilently(ElementState::ENABLED
);
41 HTMLOptionElement::~HTMLOptionElement() = default;
43 NS_IMPL_ELEMENT_CLONE(HTMLOptionElement
)
45 mozilla::dom::HTMLFormElement
* HTMLOptionElement::GetForm() {
46 HTMLSelectElement
* selectControl
= GetSelect();
47 return selectControl
? selectControl
->GetForm() : nullptr;
50 void HTMLOptionElement::SetSelectedInternal(bool aValue
, bool aNotify
) {
51 mSelectedChanged
= true;
52 SetStates(ElementState::CHECKED
, aValue
, aNotify
);
55 void HTMLOptionElement::OptGroupDisabledChanged(bool aNotify
) {
56 UpdateDisabledState(aNotify
);
59 void HTMLOptionElement::UpdateDisabledState(bool aNotify
) {
60 bool isDisabled
= HasAttr(nsGkAtoms::disabled
);
63 nsIContent
* parent
= GetParent();
64 if (auto optGroupElement
= HTMLOptGroupElement::FromNodeOrNull(parent
)) {
65 isDisabled
= optGroupElement
->IsDisabled();
69 ElementState disabledStates
;
71 disabledStates
|= ElementState::DISABLED
;
73 disabledStates
|= ElementState::ENABLED
;
76 ElementState oldDisabledStates
= State() & ElementState::DISABLED_STATES
;
77 ElementState changedStates
= disabledStates
^ oldDisabledStates
;
79 if (!changedStates
.IsEmpty()) {
80 ToggleStates(changedStates
, aNotify
);
84 void HTMLOptionElement::SetSelected(bool aValue
) {
85 // Note: The select content obj maintains all the PresState
86 // so defer to it to get the answer
87 HTMLSelectElement
* selectInt
= GetSelect();
89 int32_t index
= Index();
90 HTMLSelectElement::OptionFlags mask
{
91 HTMLSelectElement::OptionFlag::SetDisabled
,
92 HTMLSelectElement::OptionFlag::Notify
};
94 mask
+= HTMLSelectElement::OptionFlag::IsSelected
;
97 // This should end up calling SetSelectedInternal
98 selectInt
->SetOptionsSelectedByIndex(index
, index
, mask
);
100 SetSelectedInternal(aValue
, true);
104 int32_t HTMLOptionElement::Index() {
105 static int32_t defaultIndex
= 0;
107 // Only select elements can contain a list of options.
108 HTMLSelectElement
* selectElement
= GetSelect();
109 if (!selectElement
) {
113 HTMLOptionsCollection
* options
= selectElement
->GetOptions();
118 int32_t index
= defaultIndex
;
119 MOZ_ALWAYS_SUCCEEDS(options
->GetOptionIndex(this, 0, true, &index
));
123 nsChangeHint
HTMLOptionElement::GetAttributeChangeHint(const nsAtom
* aAttribute
,
124 int32_t aModType
) const {
125 nsChangeHint retval
=
126 nsGenericHTMLElement::GetAttributeChangeHint(aAttribute
, aModType
);
128 if (aAttribute
== nsGkAtoms::label
) {
129 retval
|= nsChangeHint_ReconstructFrame
;
130 } else if (aAttribute
== nsGkAtoms::text
) {
131 retval
|= NS_STYLE_HINT_REFLOW
;
136 void HTMLOptionElement::BeforeSetAttr(int32_t aNamespaceID
, nsAtom
* aName
,
137 const nsAttrValue
* aValue
, bool aNotify
) {
138 nsGenericHTMLElement::BeforeSetAttr(aNamespaceID
, aName
, aValue
, aNotify
);
140 if (aNamespaceID
!= kNameSpaceID_None
|| aName
!= nsGkAtoms::selected
||
145 // We just changed out selected state (since we look at the "selected"
146 // attribute when mSelectedChanged is false). Let's tell our select about
148 HTMLSelectElement
* selectInt
= GetSelect();
150 // If option is a child of select, SetOptionsSelectedByIndex will set the
151 // selected state if needed.
152 SetStates(ElementState::CHECKED
, !!aValue
, aNotify
);
156 NS_ASSERTION(!mSelectedChanged
, "Shouldn't be here");
158 bool inSetDefaultSelected
= mIsInSetDefaultSelected
;
159 mIsInSetDefaultSelected
= true;
161 int32_t index
= Index();
162 HTMLSelectElement::OptionFlags mask
=
163 HTMLSelectElement::OptionFlag::SetDisabled
;
165 mask
+= HTMLSelectElement::OptionFlag::IsSelected
;
169 mask
+= HTMLSelectElement::OptionFlag::Notify
;
172 // This can end up calling SetSelectedInternal if our selected state needs to
173 // change, which we will allow to take effect so that parts of
174 // SetOptionsSelectedByIndex that might depend on it working don't get
176 selectInt
->SetOptionsSelectedByIndex(index
, index
, mask
);
178 // Now reset our members; when we finish the attr set we'll end up with the
179 // rigt selected state.
180 mIsInSetDefaultSelected
= inSetDefaultSelected
;
181 // the selected state might have been changed by SetOptionsSelectedByIndex,
182 // possibly more than once; make sure our mSelectedChanged state is set back
184 mSelectedChanged
= false;
187 void HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
188 const nsAttrValue
* aValue
,
189 const nsAttrValue
* aOldValue
,
190 nsIPrincipal
* aSubjectPrincipal
,
192 if (aNameSpaceID
== kNameSpaceID_None
) {
193 if (aName
== nsGkAtoms::disabled
) {
194 UpdateDisabledState(aNotify
);
197 if (aName
== nsGkAtoms::value
&& Selected()) {
198 // Since this option is selected, changing value may have changed missing
199 // validity state of the select element
200 if (HTMLSelectElement
* select
= GetSelect()) {
201 select
->UpdateValueMissingValidityState();
205 if (aName
== nsGkAtoms::selected
) {
206 SetStates(ElementState::DEFAULT
, !!aValue
, aNotify
);
210 return nsGenericHTMLElement::AfterSetAttr(
211 aNameSpaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
214 void HTMLOptionElement::GetText(nsAString
& aText
) {
217 nsIContent
* child
= nsINode::GetFirstChild();
219 if (Text
* textChild
= child
->GetAsText()) {
220 textChild
->AppendTextTo(text
);
222 if (child
->IsHTMLElement(nsGkAtoms::script
) ||
223 child
->IsSVGElement(nsGkAtoms::script
)) {
224 child
= child
->GetNextNonChildNode(this);
226 child
= child
->GetNextNode(this);
230 // XXX No CompressWhitespace for nsAString. Sad.
231 text
.CompressWhitespace(true, true);
235 void HTMLOptionElement::SetText(const nsAString
& aText
, ErrorResult
& aRv
) {
236 aRv
= nsContentUtils::SetNodeTextContent(this, aText
, false);
239 nsresult
HTMLOptionElement::BindToTree(BindContext
& aContext
,
241 nsresult rv
= nsGenericHTMLElement::BindToTree(aContext
, aParent
);
242 NS_ENSURE_SUCCESS(rv
, rv
);
244 // Our new parent might change :disabled/:enabled state.
245 UpdateDisabledState(false);
250 void HTMLOptionElement::UnbindFromTree(UnbindContext
& aContext
) {
251 nsGenericHTMLElement::UnbindFromTree(aContext
);
253 // Our previous parent could have been involved in :disabled/:enabled state.
254 UpdateDisabledState(false);
257 // Get the select content element that contains this option
258 HTMLSelectElement
* HTMLOptionElement::GetSelect() {
259 nsIContent
* parent
= GetParent();
264 HTMLSelectElement
* select
= HTMLSelectElement::FromNode(parent
);
269 if (!parent
->IsHTMLElement(nsGkAtoms::optgroup
)) {
273 return HTMLSelectElement::FromNodeOrNull(parent
->GetParent());
276 already_AddRefed
<HTMLOptionElement
> HTMLOptionElement::Option(
277 const GlobalObject
& aGlobal
, const nsAString
& aText
,
278 const Optional
<nsAString
>& aValue
, bool aDefaultSelected
, bool aSelected
,
279 ErrorResult
& aError
) {
280 nsCOMPtr
<nsPIDOMWindowInner
> win
= do_QueryInterface(aGlobal
.GetAsSupports());
282 if (!win
|| !(doc
= win
->GetExtantDoc())) {
283 aError
.Throw(NS_ERROR_FAILURE
);
287 RefPtr
<mozilla::dom::NodeInfo
> nodeInfo
= doc
->NodeInfoManager()->GetNodeInfo(
288 nsGkAtoms::option
, nullptr, kNameSpaceID_XHTML
, ELEMENT_NODE
);
290 auto* nim
= nodeInfo
->NodeInfoManager();
291 RefPtr
<HTMLOptionElement
> option
=
292 new (nim
) HTMLOptionElement(nodeInfo
.forget());
294 if (!aText
.IsEmpty()) {
295 // Create a new text node and append it to the option
296 RefPtr
<nsTextNode
> textContent
= new (option
->NodeInfo()->NodeInfoManager())
297 nsTextNode(option
->NodeInfo()->NodeInfoManager());
299 textContent
->SetText(aText
, false);
301 option
->AppendChildTo(textContent
, false, aError
);
302 if (aError
.Failed()) {
307 if (aValue
.WasPassed()) {
308 // Set the value attribute for this element. We're calling SetAttr
309 // directly because we want to pass aNotify == false.
310 aError
= option
->SetAttr(kNameSpaceID_None
, nsGkAtoms::value
,
311 aValue
.Value(), false);
312 if (aError
.Failed()) {
317 if (aDefaultSelected
) {
318 // We're calling SetAttr directly because we want to pass
321 option
->SetAttr(kNameSpaceID_None
, nsGkAtoms::selected
, u
""_ns
, false);
322 if (aError
.Failed()) {
327 option
->SetSelected(aSelected
);
328 option
->SetSelectedChanged(false);
330 return option
.forget();
333 nsresult
HTMLOptionElement::CopyInnerTo(Element
* aDest
) {
334 nsresult rv
= nsGenericHTMLElement::CopyInnerTo(aDest
);
335 NS_ENSURE_SUCCESS(rv
, rv
);
337 if (aDest
->OwnerDoc()->IsStaticDocument()) {
338 static_cast<HTMLOptionElement
*>(aDest
)->SetSelected(Selected());
343 JSObject
* HTMLOptionElement::WrapNode(JSContext
* aCx
,
344 JS::Handle
<JSObject
*> aGivenProto
) {
345 return HTMLOptionElement_Binding::Wrap(aCx
, this, aGivenProto
);
348 } // namespace mozilla::dom