Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / html / HTMLOptionElement.cpp
blob5a9762de89f55b0b6928e77758dd5aeb9eb10f72
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"
21 #include "nsCOMPtr.h"
22 #include "nsContentCreatorFunctions.h"
23 #include "mozAutoDocUpdate.h"
24 #include "nsTextNode.h"
26 /**
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);
62 if (!isDisabled) {
63 nsIContent* parent = GetParent();
64 if (auto optGroupElement = HTMLOptGroupElement::FromNodeOrNull(parent)) {
65 isDisabled = optGroupElement->IsDisabled();
69 ElementState disabledStates;
70 if (isDisabled) {
71 disabledStates |= ElementState::DISABLED;
72 } else {
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();
88 if (selectInt) {
89 int32_t index = Index();
90 HTMLSelectElement::OptionFlags mask{
91 HTMLSelectElement::OptionFlag::SetDisabled,
92 HTMLSelectElement::OptionFlag::Notify};
93 if (aValue) {
94 mask += HTMLSelectElement::OptionFlag::IsSelected;
97 // This should end up calling SetSelectedInternal
98 selectInt->SetOptionsSelectedByIndex(index, index, mask);
99 } else {
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) {
110 return defaultIndex;
113 HTMLOptionsCollection* options = selectElement->GetOptions();
114 if (!options) {
115 return defaultIndex;
118 int32_t index = defaultIndex;
119 MOZ_ALWAYS_SUCCEEDS(options->GetOptionIndex(this, 0, true, &index));
120 return 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;
133 return retval;
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 ||
141 mSelectedChanged) {
142 return;
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
147 // it.
148 HTMLSelectElement* selectInt = GetSelect();
149 if (!selectInt) {
150 // If option is a child of select, SetOptionsSelectedByIndex will set the
151 // selected state if needed.
152 SetStates(ElementState::CHECKED, !!aValue, aNotify);
153 return;
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;
164 if (aValue) {
165 mask += HTMLSelectElement::OptionFlag::IsSelected;
168 if (aNotify) {
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
175 // confused.
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
183 // correctly.
184 mSelectedChanged = false;
187 void HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
188 const nsAttrValue* aValue,
189 const nsAttrValue* aOldValue,
190 nsIPrincipal* aSubjectPrincipal,
191 bool aNotify) {
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) {
215 nsAutoString text;
217 nsIContent* child = nsINode::GetFirstChild();
218 while (child) {
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);
225 } else {
226 child = child->GetNextNode(this);
230 // XXX No CompressWhitespace for nsAString. Sad.
231 text.CompressWhitespace(true, true);
232 aText = text;
235 void HTMLOptionElement::SetText(const nsAString& aText, ErrorResult& aRv) {
236 aRv = nsContentUtils::SetNodeTextContent(this, aText, false);
239 nsresult HTMLOptionElement::BindToTree(BindContext& aContext,
240 nsINode& aParent) {
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);
247 return NS_OK;
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();
260 if (!parent) {
261 return nullptr;
264 HTMLSelectElement* select = HTMLSelectElement::FromNode(parent);
265 if (select) {
266 return select;
269 if (!parent->IsHTMLElement(nsGkAtoms::optgroup)) {
270 return nullptr;
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());
281 Document* doc;
282 if (!win || !(doc = win->GetExtantDoc())) {
283 aError.Throw(NS_ERROR_FAILURE);
284 return nullptr;
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()) {
303 return nullptr;
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()) {
313 return nullptr;
317 if (aDefaultSelected) {
318 // We're calling SetAttr directly because we want to pass
319 // aNotify == false.
320 aError =
321 option->SetAttr(kNameSpaceID_None, nsGkAtoms::selected, u""_ns, false);
322 if (aError.Failed()) {
323 return nullptr;
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());
340 return NS_OK;
343 JSObject* HTMLOptionElement::WrapNode(JSContext* aCx,
344 JS::Handle<JSObject*> aGivenProto) {
345 return HTMLOptionElement_Binding::Wrap(aCx, this, aGivenProto);
348 } // namespace mozilla::dom