Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/dom/HTMLOptionElement.h"
8 : #include "mozilla/dom/HTMLOptionElementBinding.h"
9 : #include "mozilla/dom/HTMLSelectElement.h"
10 : #include "nsIDOMHTMLOptGroupElement.h"
11 : #include "nsIDOMHTMLFormElement.h"
12 : #include "nsGkAtoms.h"
13 : #include "nsStyleConsts.h"
14 : #include "nsIFormControl.h"
15 : #include "nsIForm.h"
16 : #include "nsIDOMNode.h"
17 : #include "nsIDOMHTMLCollection.h"
18 : #include "nsISelectControlFrame.h"
19 :
20 : // Notify/query select frame for selected state
21 : #include "nsIFormControlFrame.h"
22 : #include "nsIDocument.h"
23 : #include "nsIDOMHTMLSelectElement.h"
24 : #include "nsNodeInfoManager.h"
25 : #include "nsCOMPtr.h"
26 : #include "mozilla/EventStates.h"
27 : #include "nsContentCreatorFunctions.h"
28 : #include "mozAutoDocUpdate.h"
29 : #include "nsTextNode.h"
30 :
31 : /**
32 : * Implementation of <option>
33 : */
34 :
35 0 : NS_IMPL_NS_NEW_HTML_ELEMENT(Option)
36 :
37 : namespace mozilla {
38 : namespace dom {
39 :
40 0 : HTMLOptionElement::HTMLOptionElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
41 : : nsGenericHTMLElement(aNodeInfo),
42 : mSelectedChanged(false),
43 : mIsSelected(false),
44 0 : mIsInSetDefaultSelected(false)
45 : {
46 : // We start off enabled
47 0 : AddStatesSilently(NS_EVENT_STATE_ENABLED);
48 0 : }
49 :
50 0 : HTMLOptionElement::~HTMLOptionElement()
51 : {
52 0 : }
53 :
54 0 : NS_IMPL_ISUPPORTS_INHERITED(HTMLOptionElement, nsGenericHTMLElement,
55 : nsIDOMHTMLOptionElement)
56 :
57 0 : NS_IMPL_ELEMENT_CLONE(HTMLOptionElement)
58 :
59 :
60 : NS_IMETHODIMP
61 0 : HTMLOptionElement::GetForm(nsIDOMHTMLFormElement** aForm)
62 : {
63 0 : NS_IF_ADDREF(*aForm = GetForm());
64 0 : return NS_OK;
65 : }
66 :
67 : mozilla::dom::HTMLFormElement*
68 0 : HTMLOptionElement::GetForm()
69 : {
70 0 : HTMLSelectElement* selectControl = GetSelect();
71 0 : return selectControl ? selectControl->GetForm() : nullptr;
72 : }
73 :
74 : void
75 0 : HTMLOptionElement::SetSelectedInternal(bool aValue, bool aNotify)
76 : {
77 0 : mSelectedChanged = true;
78 0 : mIsSelected = aValue;
79 :
80 : // When mIsInSetDefaultSelected is true, the state change will be handled by
81 : // SetAttr/UnsetAttr.
82 0 : if (!mIsInSetDefaultSelected) {
83 0 : UpdateState(aNotify);
84 : }
85 0 : }
86 :
87 : NS_IMETHODIMP
88 0 : HTMLOptionElement::GetSelected(bool* aValue)
89 : {
90 0 : NS_ENSURE_ARG_POINTER(aValue);
91 0 : *aValue = Selected();
92 0 : return NS_OK;
93 : }
94 :
95 : NS_IMETHODIMP
96 0 : HTMLOptionElement::SetSelected(bool aValue)
97 : {
98 : // Note: The select content obj maintains all the PresState
99 : // so defer to it to get the answer
100 0 : HTMLSelectElement* selectInt = GetSelect();
101 0 : if (selectInt) {
102 0 : int32_t index = Index();
103 0 : uint32_t mask = HTMLSelectElement::SET_DISABLED | HTMLSelectElement::NOTIFY;
104 0 : if (aValue) {
105 0 : mask |= HTMLSelectElement::IS_SELECTED;
106 : }
107 :
108 : // This should end up calling SetSelectedInternal
109 0 : selectInt->SetOptionsSelectedByIndex(index, index, mask);
110 : } else {
111 0 : SetSelectedInternal(aValue, true);
112 : }
113 :
114 0 : return NS_OK;
115 : }
116 :
117 0 : NS_IMPL_BOOL_ATTR(HTMLOptionElement, DefaultSelected, selected)
118 : // GetText returns a whitespace compressed .textContent value.
119 0 : NS_IMPL_STRING_ATTR_WITH_FALLBACK(HTMLOptionElement, Label, label, GetText)
120 0 : NS_IMPL_STRING_ATTR_WITH_FALLBACK(HTMLOptionElement, Value, value, GetText)
121 0 : NS_IMPL_BOOL_ATTR(HTMLOptionElement, Disabled, disabled)
122 :
123 : NS_IMETHODIMP
124 0 : HTMLOptionElement::GetIndex(int32_t* aIndex)
125 : {
126 0 : *aIndex = Index();
127 0 : return NS_OK;
128 : }
129 :
130 : int32_t
131 0 : HTMLOptionElement::Index()
132 : {
133 : static int32_t defaultIndex = 0;
134 :
135 : // Only select elements can contain a list of options.
136 0 : HTMLSelectElement* selectElement = GetSelect();
137 0 : if (!selectElement) {
138 0 : return defaultIndex;
139 : }
140 :
141 0 : HTMLOptionsCollection* options = selectElement->GetOptions();
142 0 : if (!options) {
143 0 : return defaultIndex;
144 : }
145 :
146 0 : int32_t index = defaultIndex;
147 0 : MOZ_ALWAYS_SUCCEEDS(options->GetOptionIndex(this, 0, true, &index));
148 0 : return index;
149 : }
150 :
151 : bool
152 0 : HTMLOptionElement::Selected() const
153 : {
154 0 : return mIsSelected;
155 : }
156 :
157 : bool
158 0 : HTMLOptionElement::DefaultSelected() const
159 : {
160 0 : return HasAttr(kNameSpaceID_None, nsGkAtoms::selected);
161 : }
162 :
163 : nsChangeHint
164 0 : HTMLOptionElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
165 : int32_t aModType) const
166 : {
167 : nsChangeHint retval =
168 0 : nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
169 :
170 0 : if (aAttribute == nsGkAtoms::label ||
171 0 : aAttribute == nsGkAtoms::text) {
172 0 : retval |= NS_STYLE_HINT_REFLOW;
173 : }
174 0 : return retval;
175 : }
176 :
177 : nsresult
178 0 : HTMLOptionElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
179 : const nsAttrValueOrString* aValue,
180 : bool aNotify)
181 : {
182 0 : nsresult rv = nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName,
183 0 : aValue, aNotify);
184 0 : NS_ENSURE_SUCCESS(rv, rv);
185 :
186 0 : if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::selected ||
187 0 : mSelectedChanged) {
188 0 : return NS_OK;
189 : }
190 :
191 : // We just changed out selected state (since we look at the "selected"
192 : // attribute when mSelectedChanged is false). Let's tell our select about
193 : // it.
194 0 : HTMLSelectElement* selectInt = GetSelect();
195 0 : if (!selectInt) {
196 : // If option is a child of select, SetOptionsSelectedByIndex will set
197 : // mIsSelected if needed.
198 0 : mIsSelected = aValue;
199 0 : return NS_OK;
200 : }
201 :
202 0 : NS_ASSERTION(!mSelectedChanged, "Shouldn't be here");
203 :
204 0 : bool inSetDefaultSelected = mIsInSetDefaultSelected;
205 0 : mIsInSetDefaultSelected = true;
206 :
207 0 : int32_t index = Index();
208 0 : uint32_t mask = HTMLSelectElement::SET_DISABLED;
209 0 : if (aValue) {
210 0 : mask |= HTMLSelectElement::IS_SELECTED;
211 : }
212 :
213 0 : if (aNotify) {
214 0 : mask |= HTMLSelectElement::NOTIFY;
215 : }
216 :
217 : // This can end up calling SetSelectedInternal if our selected state needs to
218 : // change, which we will allow to take effect so that parts of
219 : // SetOptionsSelectedByIndex that might depend on it working don't get
220 : // confused.
221 0 : selectInt->SetOptionsSelectedByIndex(index, index, mask);
222 :
223 : // Now reset our members; when we finish the attr set we'll end up with the
224 : // rigt selected state.
225 0 : mIsInSetDefaultSelected = inSetDefaultSelected;
226 : // mIsSelected might have been changed by SetOptionsSelectedByIndex. Possibly
227 : // more than once; make sure our mSelectedChanged state is set back correctly.
228 0 : mSelectedChanged = false;
229 :
230 0 : return NS_OK;
231 : }
232 :
233 : nsresult
234 0 : HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
235 : const nsAttrValue* aValue,
236 : const nsAttrValue* aOldValue, bool aNotify)
237 : {
238 0 : if (aNameSpaceID == kNameSpaceID_None &&
239 0 : aName == nsGkAtoms::value && Selected()) {
240 : // Since this option is selected, changing value
241 : // may have changed missing validity state of the
242 : // Select element
243 0 : HTMLSelectElement* select = GetSelect();
244 0 : if (select) {
245 0 : select->UpdateValueMissingValidityState();
246 : }
247 : }
248 :
249 0 : return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
250 0 : aValue, aOldValue, aNotify);
251 : }
252 :
253 : NS_IMETHODIMP
254 0 : HTMLOptionElement::GetText(nsAString& aText)
255 : {
256 0 : nsAutoString text;
257 :
258 0 : nsIContent* child = nsINode::GetFirstChild();
259 0 : while (child) {
260 0 : if (child->NodeType() == nsIDOMNode::TEXT_NODE ||
261 0 : child->NodeType() == nsIDOMNode::CDATA_SECTION_NODE) {
262 0 : child->AppendTextTo(text);
263 : }
264 0 : if (child->IsHTMLElement(nsGkAtoms::script) ||
265 0 : child->IsSVGElement(nsGkAtoms::script)) {
266 0 : child = child->GetNextNonChildNode(this);
267 : } else {
268 0 : child = child->GetNextNode(this);
269 : }
270 : }
271 :
272 : // XXX No CompressWhitespace for nsAString. Sad.
273 0 : text.CompressWhitespace(true, true);
274 0 : aText = text;
275 :
276 0 : return NS_OK;
277 : }
278 :
279 : NS_IMETHODIMP
280 0 : HTMLOptionElement::SetText(const nsAString& aText)
281 : {
282 0 : return nsContentUtils::SetNodeTextContent(this, aText, true);
283 : }
284 :
285 : nsresult
286 0 : HTMLOptionElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
287 : nsIContent* aBindingParent,
288 : bool aCompileEventHandlers)
289 : {
290 0 : nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
291 : aBindingParent,
292 0 : aCompileEventHandlers);
293 0 : NS_ENSURE_SUCCESS(rv, rv);
294 :
295 : // Our new parent might change :disabled/:enabled state.
296 0 : UpdateState(false);
297 :
298 0 : return NS_OK;
299 : }
300 :
301 : void
302 0 : HTMLOptionElement::UnbindFromTree(bool aDeep, bool aNullParent)
303 : {
304 0 : nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
305 :
306 : // Our previous parent could have been involved in :disabled/:enabled state.
307 0 : UpdateState(false);
308 0 : }
309 :
310 : EventStates
311 0 : HTMLOptionElement::IntrinsicState() const
312 : {
313 0 : EventStates state = nsGenericHTMLElement::IntrinsicState();
314 0 : if (Selected()) {
315 0 : state |= NS_EVENT_STATE_CHECKED;
316 : }
317 0 : if (DefaultSelected()) {
318 0 : state |= NS_EVENT_STATE_DEFAULT;
319 : }
320 :
321 : // An <option> is disabled if it has @disabled set or if it's <optgroup> has
322 : // @disabled set.
323 0 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
324 0 : state |= NS_EVENT_STATE_DISABLED;
325 0 : state &= ~NS_EVENT_STATE_ENABLED;
326 : } else {
327 0 : nsIContent* parent = GetParent();
328 0 : if (parent && parent->IsHTMLElement(nsGkAtoms::optgroup) &&
329 0 : parent->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
330 0 : state |= NS_EVENT_STATE_DISABLED;
331 0 : state &= ~NS_EVENT_STATE_ENABLED;
332 : } else {
333 0 : state &= ~NS_EVENT_STATE_DISABLED;
334 0 : state |= NS_EVENT_STATE_ENABLED;
335 : }
336 : }
337 :
338 0 : return state;
339 : }
340 :
341 : // Get the select content element that contains this option
342 : HTMLSelectElement*
343 0 : HTMLOptionElement::GetSelect()
344 : {
345 0 : nsIContent* parent = GetParent();
346 0 : if (!parent) {
347 0 : return nullptr;
348 : }
349 :
350 0 : HTMLSelectElement* select = HTMLSelectElement::FromContent(parent);
351 0 : if (select) {
352 0 : return select;
353 : }
354 :
355 0 : if (!parent->IsHTMLElement(nsGkAtoms::optgroup)) {
356 0 : return nullptr;
357 : }
358 :
359 0 : return HTMLSelectElement::FromContentOrNull(parent->GetParent());
360 : }
361 :
362 : already_AddRefed<HTMLOptionElement>
363 0 : HTMLOptionElement::Option(const GlobalObject& aGlobal,
364 : const nsAString& aText,
365 : const Optional<nsAString>& aValue,
366 : bool aDefaultSelected,
367 : bool aSelected,
368 : ErrorResult& aError)
369 : {
370 0 : nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
371 : nsIDocument* doc;
372 0 : if (!win || !(doc = win->GetExtantDoc())) {
373 0 : aError.Throw(NS_ERROR_FAILURE);
374 0 : return nullptr;
375 : }
376 :
377 : already_AddRefed<mozilla::dom::NodeInfo> nodeInfo =
378 : doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::option, nullptr,
379 : kNameSpaceID_XHTML,
380 0 : nsIDOMNode::ELEMENT_NODE);
381 :
382 0 : RefPtr<HTMLOptionElement> option = new HTMLOptionElement(nodeInfo);
383 :
384 0 : if (!aText.IsEmpty()) {
385 : // Create a new text node and append it to the option
386 : RefPtr<nsTextNode> textContent =
387 0 : new nsTextNode(option->NodeInfo()->NodeInfoManager());
388 :
389 0 : textContent->SetText(aText, false);
390 :
391 0 : aError = option->AppendChildTo(textContent, false);
392 0 : if (aError.Failed()) {
393 0 : return nullptr;
394 : }
395 : }
396 :
397 0 : if (aValue.WasPassed()) {
398 : // Set the value attribute for this element. We're calling SetAttr
399 : // directly because we want to pass aNotify == false.
400 0 : aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
401 0 : aValue.Value(), false);
402 0 : if (aError.Failed()) {
403 0 : return nullptr;
404 : }
405 : }
406 :
407 0 : if (aDefaultSelected) {
408 : // We're calling SetAttr directly because we want to pass
409 : // aNotify == false.
410 0 : aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::selected,
411 0 : EmptyString(), false);
412 0 : if (aError.Failed()) {
413 0 : return nullptr;
414 : }
415 : }
416 :
417 0 : option->SetSelected(aSelected, aError);
418 0 : if (aError.Failed()) {
419 0 : return nullptr;
420 : }
421 :
422 0 : option->SetSelectedChanged(false);
423 :
424 0 : return option.forget();
425 : }
426 :
427 : nsresult
428 0 : HTMLOptionElement::CopyInnerTo(Element* aDest, bool aPreallocateChildren)
429 : {
430 0 : nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest, aPreallocateChildren);
431 0 : NS_ENSURE_SUCCESS(rv, rv);
432 :
433 0 : if (aDest->OwnerDoc()->IsStaticDocument()) {
434 0 : static_cast<HTMLOptionElement*>(aDest)->SetSelected(Selected());
435 : }
436 0 : return NS_OK;
437 : }
438 :
439 : JSObject*
440 0 : HTMLOptionElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
441 : {
442 0 : return HTMLOptionElementBinding::Wrap(aCx, this, aGivenProto);
443 : }
444 :
445 : } // namespace dom
446 : } // namespace mozilla
|