1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=2 sw=2 et tw=78: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla Communicator client code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications Corporation.
20 : * Portions created by the Initial Developer are Copyright (C) 1998
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Pierre Phaneuf <pp@ludusdesign.com>
25 : * Mats Palmgren <mats.palmgren@bredband.net>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either of the GNU General Public License Version 2 or later (the "GPL"),
29 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "nsHTMLOptionElement.h"
42 : #include "nsHTMLSelectElement.h"
43 : #include "nsIDOMHTMLOptGroupElement.h"
44 : #include "nsIDOMHTMLFormElement.h"
45 : #include "nsIDOMEventTarget.h"
46 : #include "nsGkAtoms.h"
47 : #include "nsStyleConsts.h"
48 : #include "nsIFormControl.h"
49 : #include "nsIForm.h"
50 : #include "nsIDOMNode.h"
51 : #include "nsIDOMHTMLCollection.h"
52 : #include "nsISelectControlFrame.h"
53 :
54 : // Notify/query select frame for selected state
55 : #include "nsIFormControlFrame.h"
56 : #include "nsIDocument.h"
57 : #include "nsIFrame.h"
58 : #include "nsIDOMHTMLSelectElement.h"
59 : #include "nsNodeInfoManager.h"
60 : #include "nsCOMPtr.h"
61 : #include "nsEventStates.h"
62 : #include "nsIDocument.h"
63 : #include "nsIDOMDocument.h"
64 : #include "nsContentCreatorFunctions.h"
65 : #include "mozAutoDocUpdate.h"
66 :
67 : using namespace mozilla::dom;
68 :
69 : /**
70 : * Implementation of <option>
71 : */
72 :
73 : nsGenericHTMLElement*
74 0 : NS_NewHTMLOptionElement(already_AddRefed<nsINodeInfo> aNodeInfo,
75 : FromParser aFromParser)
76 : {
77 : /*
78 : * nsHTMLOptionElement's will be created without a nsINodeInfo passed in
79 : * if someone says "var opt = new Option();" in JavaScript, in a case like
80 : * that we request the nsINodeInfo from the document's nodeinfo list.
81 : */
82 0 : nsCOMPtr<nsINodeInfo> nodeInfo(aNodeInfo);
83 0 : if (!nodeInfo) {
84 : nsCOMPtr<nsIDocument> doc =
85 0 : do_QueryInterface(nsContentUtils::GetDocumentFromCaller());
86 0 : NS_ENSURE_TRUE(doc, nsnull);
87 :
88 : nodeInfo = doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::option, nsnull,
89 : kNameSpaceID_XHTML,
90 0 : nsIDOMNode::ELEMENT_NODE);
91 0 : NS_ENSURE_TRUE(nodeInfo, nsnull);
92 : }
93 :
94 0 : return new nsHTMLOptionElement(nodeInfo.forget());
95 : }
96 :
97 0 : nsHTMLOptionElement::nsHTMLOptionElement(already_AddRefed<nsINodeInfo> aNodeInfo)
98 : : nsGenericHTMLElement(aNodeInfo),
99 : mSelectedChanged(false),
100 : mIsSelected(false),
101 0 : mIsInSetDefaultSelected(false)
102 : {
103 : // We start off enabled
104 0 : AddStatesSilently(NS_EVENT_STATE_ENABLED);
105 0 : }
106 :
107 0 : nsHTMLOptionElement::~nsHTMLOptionElement()
108 : {
109 0 : }
110 :
111 : // ISupports
112 :
113 :
114 0 : NS_IMPL_ADDREF_INHERITED(nsHTMLOptionElement, nsGenericElement)
115 0 : NS_IMPL_RELEASE_INHERITED(nsHTMLOptionElement, nsGenericElement)
116 :
117 :
118 0 : DOMCI_NODE_DATA(HTMLOptionElement, nsHTMLOptionElement)
119 :
120 : // QueryInterface implementation for nsHTMLOptionElement
121 0 : NS_INTERFACE_TABLE_HEAD(nsHTMLOptionElement)
122 0 : NS_HTML_CONTENT_INTERFACE_TABLE2(nsHTMLOptionElement,
123 : nsIDOMHTMLOptionElement,
124 : nsIJSNativeInitializer)
125 0 : NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLOptionElement,
126 : nsGenericHTMLElement)
127 0 : NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLOptionElement)
128 :
129 :
130 0 : NS_IMPL_ELEMENT_CLONE(nsHTMLOptionElement)
131 :
132 :
133 : NS_IMETHODIMP
134 0 : nsHTMLOptionElement::GetForm(nsIDOMHTMLFormElement** aForm)
135 : {
136 0 : NS_ENSURE_ARG_POINTER(aForm);
137 0 : *aForm = nsnull;
138 :
139 0 : nsHTMLSelectElement* selectControl = GetSelect();
140 :
141 0 : if (selectControl) {
142 0 : selectControl->GetForm(aForm);
143 : }
144 :
145 0 : return NS_OK;
146 : }
147 :
148 : void
149 0 : nsHTMLOptionElement::SetSelectedInternal(bool aValue, bool aNotify)
150 : {
151 0 : mSelectedChanged = true;
152 0 : mIsSelected = aValue;
153 :
154 : // When mIsInSetDefaultSelected is true, the state change will be handled by
155 : // SetAttr/UnsetAttr.
156 0 : if (!mIsInSetDefaultSelected) {
157 0 : UpdateState(aNotify);
158 : }
159 0 : }
160 :
161 : NS_IMETHODIMP
162 0 : nsHTMLOptionElement::GetSelected(bool* aValue)
163 : {
164 0 : NS_ENSURE_ARG_POINTER(aValue);
165 0 : *aValue = Selected();
166 0 : return NS_OK;
167 : }
168 :
169 : NS_IMETHODIMP
170 0 : nsHTMLOptionElement::SetSelected(bool aValue)
171 : {
172 : // Note: The select content obj maintains all the PresState
173 : // so defer to it to get the answer
174 0 : nsHTMLSelectElement* selectInt = GetSelect();
175 0 : if (selectInt) {
176 : PRInt32 index;
177 0 : GetIndex(&index);
178 : // This should end up calling SetSelectedInternal
179 : return selectInt->SetOptionsSelectedByIndex(index, index, aValue,
180 : false, true, true,
181 0 : nsnull);
182 : } else {
183 0 : SetSelectedInternal(aValue, true);
184 0 : return NS_OK;
185 : }
186 :
187 : return NS_OK;
188 : }
189 :
190 0 : NS_IMPL_BOOL_ATTR(nsHTMLOptionElement, DefaultSelected, selected)
191 : // GetText returns a whitespace compressed .textContent value.
192 0 : NS_IMPL_STRING_ATTR_WITH_FALLBACK(nsHTMLOptionElement, Label, label, GetText)
193 0 : NS_IMPL_STRING_ATTR_WITH_FALLBACK(nsHTMLOptionElement, Value, value, GetText)
194 0 : NS_IMPL_BOOL_ATTR(nsHTMLOptionElement, Disabled, disabled)
195 :
196 : NS_IMETHODIMP
197 0 : nsHTMLOptionElement::GetIndex(PRInt32* aIndex)
198 : {
199 : // When the element is not in a list of options, the index is 0.
200 0 : *aIndex = 0;
201 :
202 : // Only select elements can contain a list of options.
203 0 : nsHTMLSelectElement* selectElement = GetSelect();
204 0 : if (!selectElement) {
205 0 : return NS_OK;
206 : }
207 :
208 0 : nsHTMLOptionCollection* options = selectElement->GetOptions();
209 0 : if (!options) {
210 0 : return NS_OK;
211 : }
212 :
213 : // aIndex will not be set if GetOptionsIndex fails.
214 0 : return options->GetOptionIndex(this, 0, true, aIndex);
215 : }
216 :
217 : bool
218 0 : nsHTMLOptionElement::Selected() const
219 : {
220 : // If we haven't been explictly selected or deselected, use our default value
221 0 : if (!mSelectedChanged) {
222 0 : return DefaultSelected();
223 : }
224 :
225 0 : return mIsSelected;
226 : }
227 :
228 : bool
229 0 : nsHTMLOptionElement::DefaultSelected() const
230 : {
231 0 : return HasAttr(kNameSpaceID_None, nsGkAtoms::selected);
232 : }
233 :
234 : nsChangeHint
235 0 : nsHTMLOptionElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
236 : PRInt32 aModType) const
237 : {
238 : nsChangeHint retval =
239 0 : nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
240 :
241 0 : if (aAttribute == nsGkAtoms::label ||
242 : aAttribute == nsGkAtoms::text) {
243 0 : NS_UpdateHint(retval, NS_STYLE_HINT_REFLOW);
244 : }
245 0 : return retval;
246 : }
247 :
248 : nsresult
249 0 : nsHTMLOptionElement::BeforeSetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
250 : const nsAttrValueOrString* aValue,
251 : bool aNotify)
252 : {
253 : nsresult rv = nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName,
254 0 : aValue, aNotify);
255 0 : NS_ENSURE_SUCCESS(rv, rv);
256 :
257 0 : if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::selected ||
258 : mSelectedChanged) {
259 0 : return NS_OK;
260 : }
261 :
262 : // We just changed out selected state (since we look at the "selected"
263 : // attribute when mSelectedChanged is false). Let's tell our select about
264 : // it.
265 0 : nsHTMLSelectElement* selectInt = GetSelect();
266 0 : if (!selectInt) {
267 0 : return NS_OK;
268 : }
269 :
270 : // Note that at this point mSelectedChanged is false and as long as that's
271 : // true it doesn't matter what value mIsSelected has.
272 0 : NS_ASSERTION(!mSelectedChanged, "Shouldn't be here");
273 :
274 0 : bool newSelected = (aValue != nsnull);
275 0 : bool inSetDefaultSelected = mIsInSetDefaultSelected;
276 0 : mIsInSetDefaultSelected = true;
277 :
278 : PRInt32 index;
279 0 : GetIndex(&index);
280 : // This should end up calling SetSelectedInternal, which we will allow to
281 : // take effect so that parts of SetOptionsSelectedByIndex that might depend
282 : // on it working don't get confused.
283 : rv = selectInt->SetOptionsSelectedByIndex(index, index, newSelected,
284 : false, true, aNotify,
285 0 : nsnull);
286 :
287 : // Now reset our members; when we finish the attr set we'll end up with the
288 : // rigt selected state.
289 0 : mIsInSetDefaultSelected = inSetDefaultSelected;
290 0 : mSelectedChanged = false;
291 : // mIsSelected doesn't matter while mSelectedChanged is false
292 :
293 0 : return rv;
294 : }
295 :
296 : NS_IMETHODIMP
297 0 : nsHTMLOptionElement::GetText(nsAString& aText)
298 : {
299 0 : nsAutoString text;
300 0 : nsContentUtils::GetNodeTextContent(this, false, text);
301 :
302 : // XXX No CompressWhitespace for nsAString. Sad.
303 0 : text.CompressWhitespace(true, true);
304 0 : aText = text;
305 :
306 0 : return NS_OK;
307 : }
308 :
309 : NS_IMETHODIMP
310 0 : nsHTMLOptionElement::SetText(const nsAString& aText)
311 : {
312 0 : return nsContentUtils::SetNodeTextContent(this, aText, true);
313 : }
314 :
315 : nsEventStates
316 0 : nsHTMLOptionElement::IntrinsicState() const
317 : {
318 0 : nsEventStates state = nsGenericHTMLElement::IntrinsicState();
319 0 : if (Selected()) {
320 0 : state |= NS_EVENT_STATE_CHECKED;
321 : }
322 0 : if (DefaultSelected()) {
323 0 : state |= NS_EVENT_STATE_DEFAULT;
324 : }
325 :
326 0 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
327 0 : state |= NS_EVENT_STATE_DISABLED;
328 0 : state &= ~NS_EVENT_STATE_ENABLED;
329 : } else {
330 0 : state &= ~NS_EVENT_STATE_DISABLED;
331 0 : state |= NS_EVENT_STATE_ENABLED;
332 : }
333 :
334 : return state;
335 : }
336 :
337 : // Get the select content element that contains this option
338 : nsHTMLSelectElement*
339 0 : nsHTMLOptionElement::GetSelect()
340 : {
341 0 : nsIContent* parent = this;
342 0 : while ((parent = parent->GetParent()) &&
343 0 : parent->IsHTML()) {
344 0 : if (parent->Tag() == nsGkAtoms::select) {
345 0 : return nsHTMLSelectElement::FromContent(parent);
346 : }
347 0 : if (parent->Tag() != nsGkAtoms::optgroup) {
348 0 : break;
349 : }
350 : }
351 :
352 0 : return nsnull;
353 : }
354 :
355 : NS_IMETHODIMP
356 0 : nsHTMLOptionElement::Initialize(nsISupports* aOwner,
357 : JSContext* aContext,
358 : JSObject *aObj,
359 : PRUint32 argc,
360 : jsval *argv)
361 : {
362 0 : nsresult result = NS_OK;
363 :
364 0 : if (argc > 0) {
365 : // The first (optional) parameter is the text of the option
366 0 : JSString* jsstr = JS_ValueToString(aContext, argv[0]);
367 0 : if (!jsstr) {
368 0 : return NS_ERROR_FAILURE;
369 : }
370 :
371 : // Create a new text node and append it to the option
372 0 : nsCOMPtr<nsIContent> textContent;
373 0 : result = NS_NewTextNode(getter_AddRefs(textContent),
374 0 : mNodeInfo->NodeInfoManager());
375 0 : if (NS_FAILED(result)) {
376 0 : return result;
377 : }
378 :
379 : size_t length;
380 0 : const jschar *chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
381 0 : if (!chars) {
382 0 : return NS_ERROR_FAILURE;
383 : }
384 :
385 0 : textContent->SetText(chars, length, false);
386 :
387 0 : result = AppendChildTo(textContent, false);
388 0 : if (NS_FAILED(result)) {
389 0 : return result;
390 : }
391 :
392 0 : if (argc > 1) {
393 : // The second (optional) parameter is the value of the option
394 0 : jsstr = JS_ValueToString(aContext, argv[1]);
395 0 : if (!jsstr) {
396 0 : return NS_ERROR_FAILURE;
397 : }
398 :
399 : size_t length;
400 0 : const jschar *chars = JS_GetStringCharsAndLength(aContext, jsstr, &length);
401 0 : if (!chars) {
402 0 : return NS_ERROR_FAILURE;
403 : }
404 :
405 : // Set the value attribute for this element
406 0 : nsAutoString value(chars, length);
407 :
408 : result = SetAttr(kNameSpaceID_None, nsGkAtoms::value, value,
409 0 : false);
410 0 : if (NS_FAILED(result)) {
411 0 : return result;
412 : }
413 :
414 0 : if (argc > 2) {
415 : // The third (optional) parameter is the defaultSelected value
416 : JSBool defaultSelected;
417 0 : JS_ValueToBoolean(aContext, argv[2], &defaultSelected);
418 0 : if (defaultSelected) {
419 : result = SetAttr(kNameSpaceID_None, nsGkAtoms::selected,
420 0 : EmptyString(), false);
421 0 : NS_ENSURE_SUCCESS(result, result);
422 : }
423 :
424 : // XXX This is *untested* behavior. Should work though.
425 0 : if (argc > 3) {
426 : JSBool selected;
427 0 : JS_ValueToBoolean(aContext, argv[3], &selected);
428 :
429 0 : return SetSelected(selected);
430 : }
431 : }
432 : }
433 : }
434 :
435 0 : return result;
436 : }
437 :
438 : nsresult
439 0 : nsHTMLOptionElement::CopyInnerTo(nsGenericElement* aDest) const
440 : {
441 0 : nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
442 0 : NS_ENSURE_SUCCESS(rv, rv);
443 :
444 0 : if (aDest->OwnerDoc()->IsStaticDocument()) {
445 0 : static_cast<nsHTMLOptionElement*>(aDest)->SetSelected(Selected());
446 : }
447 0 : return NS_OK;
448 : }
449 :
|