1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Pierre Phaneuf <pp@ludusdesign.com>
24 : * Mats Palmgren <mats.palmgren@bredband.net>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "mozilla/Util.h"
41 :
42 : #include "nsHTMLSelectElement.h"
43 :
44 : #include "nsHTMLOptionElement.h"
45 : #include "nsIDOMEventTarget.h"
46 : #include "nsContentCreatorFunctions.h"
47 : #include "nsGkAtoms.h"
48 : #include "nsStyleConsts.h"
49 : #include "nsLayoutUtils.h"
50 : #include "nsMappedAttributes.h"
51 : #include "nsIForm.h"
52 : #include "nsFormSubmission.h"
53 : #include "nsIFormProcessor.h"
54 : #include "nsContentCreatorFunctions.h"
55 :
56 : #include "nsIDOMHTMLOptGroupElement.h"
57 : #include "nsEventStates.h"
58 : #include "nsGUIEvent.h"
59 : #include "nsIPrivateDOMEvent.h"
60 :
61 : // Notify/query select frame for selectedIndex
62 : #include "nsIDocument.h"
63 : #include "nsIFormControlFrame.h"
64 : #include "nsIComboboxControlFrame.h"
65 : #include "nsIListControlFrame.h"
66 : #include "nsIFrame.h"
67 :
68 : #include "nsDOMError.h"
69 : #include "nsServiceManagerUtils.h"
70 : #include "nsRuleData.h"
71 : #include "nsEventDispatcher.h"
72 : #include "mozilla/dom/Element.h"
73 : #include "mozAutoDocUpdate.h"
74 : #include "dombindings.h"
75 :
76 : using namespace mozilla;
77 : using namespace mozilla::dom;
78 :
79 0 : NS_IMPL_ISUPPORTS1(nsSelectState, nsSelectState)
80 : NS_DEFINE_STATIC_IID_ACCESSOR(nsSelectState, NS_SELECT_STATE_IID)
81 :
82 : //----------------------------------------------------------------------
83 : //
84 : // nsSafeOptionListMutation
85 : //
86 :
87 0 : nsSafeOptionListMutation::nsSafeOptionListMutation(nsIContent* aSelect,
88 : nsIContent* aParent,
89 : nsIContent* aKid,
90 : PRUint32 aIndex,
91 : bool aNotify)
92 : : mSelect(nsHTMLSelectElement::FromContent(aSelect))
93 : , mTopLevelMutation(false)
94 0 : , mNeedsRebuild(false)
95 : {
96 0 : if (mSelect) {
97 0 : mTopLevelMutation = !mSelect->mMutating;
98 0 : if (mTopLevelMutation) {
99 0 : mSelect->mMutating = true;
100 : } else {
101 : // This is very unfortunate, but to handle mutation events properly,
102 : // option list must be up-to-date before inserting or removing options.
103 : // Fortunately this is called only if mutation event listener
104 : // adds or removes options.
105 0 : mSelect->RebuildOptionsArray(aNotify);
106 : }
107 : nsresult rv;
108 0 : if (aKid) {
109 0 : rv = mSelect->WillAddOptions(aKid, aParent, aIndex, aNotify);
110 : } else {
111 0 : rv = mSelect->WillRemoveOptions(aParent, aIndex, aNotify);
112 : }
113 0 : mNeedsRebuild = NS_FAILED(rv);
114 : }
115 0 : }
116 :
117 0 : nsSafeOptionListMutation::~nsSafeOptionListMutation()
118 : {
119 0 : if (mSelect) {
120 0 : if (mNeedsRebuild || (mTopLevelMutation && mGuard.Mutated(1))) {
121 0 : mSelect->RebuildOptionsArray(true);
122 : }
123 0 : if (mTopLevelMutation) {
124 0 : mSelect->mMutating = false;
125 : }
126 : #ifdef DEBUG
127 0 : mSelect->VerifyOptionsArray();
128 : #endif
129 : }
130 0 : }
131 :
132 : //----------------------------------------------------------------------
133 : //
134 : // nsHTMLSelectElement
135 : //
136 :
137 : // construction, destruction
138 :
139 :
140 0 : NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Select)
141 :
142 0 : nsHTMLSelectElement::nsHTMLSelectElement(already_AddRefed<nsINodeInfo> aNodeInfo,
143 : FromParser aFromParser)
144 : : nsGenericHTMLFormElement(aNodeInfo),
145 0 : mOptions(new nsHTMLOptionCollection(this)),
146 0 : mIsDoneAddingChildren(!aFromParser),
147 : mDisabledChanged(false),
148 : mMutating(false),
149 0 : mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
150 : mSelectionHasChanged(false),
151 : mDefaultSelectionSet(false),
152 : mCanShowInvalidUI(true),
153 : mCanShowValidUI(true),
154 : mNonOptionChildren(0),
155 : mOptGroupCount(0),
156 0 : mSelectedIndex(-1)
157 : {
158 : // DoneAddingChildren() will be called later if it's from the parser,
159 : // otherwise it is
160 :
161 : // Set up our default state: enabled, optional, and valid.
162 : AddStatesSilently(NS_EVENT_STATE_ENABLED |
163 : NS_EVENT_STATE_OPTIONAL |
164 0 : NS_EVENT_STATE_VALID);
165 0 : }
166 :
167 0 : nsHTMLSelectElement::~nsHTMLSelectElement()
168 : {
169 0 : mOptions->DropReference();
170 0 : }
171 :
172 : // ISupports
173 :
174 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLSelectElement)
175 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLSelectElement,
176 : nsGenericHTMLFormElement)
177 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mOptions,
178 : nsIDOMHTMLCollection)
179 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
180 :
181 0 : NS_IMPL_ADDREF_INHERITED(nsHTMLSelectElement, nsGenericElement)
182 0 : NS_IMPL_RELEASE_INHERITED(nsHTMLSelectElement, nsGenericElement)
183 :
184 :
185 0 : DOMCI_NODE_DATA(HTMLSelectElement, nsHTMLSelectElement)
186 :
187 : // QueryInterface implementation for nsHTMLSelectElement
188 0 : NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHTMLSelectElement)
189 0 : NS_HTML_CONTENT_INTERFACE_TABLE2(nsHTMLSelectElement,
190 : nsIDOMHTMLSelectElement,
191 : nsIConstraintValidation)
192 0 : NS_HTML_CONTENT_INTERFACE_TABLE_TO_MAP_SEGUE(nsHTMLSelectElement,
193 : nsGenericHTMLFormElement)
194 0 : NS_HTML_CONTENT_INTERFACE_TABLE_TAIL_CLASSINFO(HTMLSelectElement)
195 :
196 :
197 : // nsIDOMHTMLSelectElement
198 :
199 :
200 0 : NS_IMPL_ELEMENT_CLONE(nsHTMLSelectElement)
201 :
202 : // nsIConstraintValidation
203 0 : NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(nsHTMLSelectElement)
204 :
205 : NS_IMETHODIMP
206 0 : nsHTMLSelectElement::SetCustomValidity(const nsAString& aError)
207 : {
208 0 : nsIConstraintValidation::SetCustomValidity(aError);
209 :
210 0 : UpdateState(true);
211 :
212 0 : return NS_OK;
213 : }
214 :
215 : NS_IMETHODIMP
216 0 : nsHTMLSelectElement::GetForm(nsIDOMHTMLFormElement** aForm)
217 : {
218 0 : return nsGenericHTMLFormElement::GetForm(aForm);
219 : }
220 :
221 : nsresult
222 0 : nsHTMLSelectElement::InsertChildAt(nsIContent* aKid,
223 : PRUint32 aIndex,
224 : bool aNotify)
225 : {
226 0 : nsSafeOptionListMutation safeMutation(this, this, aKid, aIndex, aNotify);
227 0 : nsresult rv = nsGenericHTMLFormElement::InsertChildAt(aKid, aIndex, aNotify);
228 0 : if (NS_FAILED(rv)) {
229 0 : safeMutation.MutationFailed();
230 : }
231 0 : return rv;
232 : }
233 :
234 : nsresult
235 0 : nsHTMLSelectElement::RemoveChildAt(PRUint32 aIndex, bool aNotify)
236 : {
237 0 : nsSafeOptionListMutation safeMutation(this, this, nsnull, aIndex, aNotify);
238 0 : nsresult rv = nsGenericHTMLFormElement::RemoveChildAt(aIndex, aNotify);
239 0 : if (NS_FAILED(rv)) {
240 0 : safeMutation.MutationFailed();
241 : }
242 0 : return rv;
243 : }
244 :
245 :
246 : // SelectElement methods
247 :
248 : nsresult
249 0 : nsHTMLSelectElement::InsertOptionsIntoList(nsIContent* aOptions,
250 : PRInt32 aListIndex,
251 : PRInt32 aDepth,
252 : bool aNotify)
253 : {
254 0 : PRInt32 insertIndex = aListIndex;
255 0 : nsresult rv = InsertOptionsIntoListRecurse(aOptions, &insertIndex, aDepth);
256 0 : NS_ENSURE_SUCCESS(rv, rv);
257 :
258 : // Deal with the selected list
259 0 : if (insertIndex - aListIndex) {
260 : // Fix the currently selected index
261 0 : if (aListIndex <= mSelectedIndex) {
262 0 : mSelectedIndex += (insertIndex - aListIndex);
263 0 : SetSelectionChanged(true, aNotify);
264 : }
265 :
266 : // Get the frame stuff for notification. No need to flush here
267 : // since if there's no frame for the select yet the select will
268 : // get into the right state once it's created.
269 0 : nsISelectControlFrame* selectFrame = nsnull;
270 0 : nsWeakFrame weakSelectFrame;
271 0 : bool didGetFrame = false;
272 :
273 : // Actually select the options if the added options warrant it
274 0 : nsCOMPtr<nsIDOMNode> optionNode;
275 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option;
276 0 : for (PRInt32 i = aListIndex; i < insertIndex; i++) {
277 : // Notify the frame that the option is added
278 0 : if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
279 0 : selectFrame = GetSelectFrame();
280 0 : weakSelectFrame = do_QueryFrame(selectFrame);
281 0 : didGetFrame = true;
282 : }
283 :
284 0 : if (selectFrame) {
285 0 : selectFrame->AddOption(i);
286 : }
287 :
288 0 : Item(i, getter_AddRefs(optionNode));
289 0 : option = do_QueryInterface(optionNode);
290 0 : if (option) {
291 : bool selected;
292 0 : option->GetSelected(&selected);
293 0 : if (selected) {
294 : // Clear all other options
295 0 : if (!HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
296 0 : SetOptionsSelectedByIndex(i, i, true, true, true, true, nsnull);
297 : }
298 :
299 : // This is sort of a hack ... we need to notify that the option was
300 : // set and change selectedIndex even though we didn't really change
301 : // its value.
302 0 : OnOptionSelected(selectFrame, i, true, false, false);
303 : }
304 : }
305 : }
306 :
307 0 : CheckSelectSomething(aNotify);
308 : }
309 :
310 0 : return NS_OK;
311 : }
312 :
313 : nsresult
314 0 : nsHTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions,
315 : PRInt32 aListIndex,
316 : PRInt32 aDepth,
317 : bool aNotify)
318 : {
319 0 : PRInt32 numRemoved = 0;
320 : nsresult rv = RemoveOptionsFromListRecurse(aOptions, aListIndex, &numRemoved,
321 0 : aDepth);
322 0 : NS_ENSURE_SUCCESS(rv, rv);
323 :
324 0 : if (numRemoved) {
325 : // Tell the widget we removed the options
326 0 : nsISelectControlFrame* selectFrame = GetSelectFrame();
327 0 : if (selectFrame) {
328 0 : nsAutoScriptBlocker scriptBlocker;
329 0 : for (PRInt32 i = aListIndex; i < aListIndex + numRemoved; ++i) {
330 0 : selectFrame->RemoveOption(i);
331 : }
332 : }
333 :
334 : // Fix the selected index
335 0 : if (aListIndex <= mSelectedIndex) {
336 0 : if (mSelectedIndex < (aListIndex+numRemoved)) {
337 : // aListIndex <= mSelectedIndex < aListIndex+numRemoved
338 : // Find a new selected index if it was one of the ones removed.
339 0 : FindSelectedIndex(aListIndex, aNotify);
340 : } else {
341 : // Shift the selected index if something in front of it was removed
342 : // aListIndex+numRemoved <= mSelectedIndex
343 0 : mSelectedIndex -= numRemoved;
344 0 : SetSelectionChanged(true, aNotify);
345 : }
346 : }
347 :
348 : // Select something in case we removed the selected option on a
349 : // single select
350 0 : if (!CheckSelectSomething(aNotify) && mSelectedIndex == -1) {
351 : // Update the validity state in case of we've just removed the last
352 : // option.
353 0 : UpdateValueMissingValidityState();
354 :
355 0 : UpdateState(aNotify);
356 : }
357 : }
358 :
359 0 : return NS_OK;
360 : }
361 :
362 : // If the document is such that recursing over these options gets us
363 : // deeper than four levels, there is something terribly wrong with the
364 : // world.
365 : nsresult
366 0 : nsHTMLSelectElement::InsertOptionsIntoListRecurse(nsIContent* aOptions,
367 : PRInt32* aInsertIndex,
368 : PRInt32 aDepth)
369 : {
370 : // We *assume* here that someone's brain has not gone horribly
371 : // wrong by putting <option> inside of <option>. I'm sorry, I'm
372 : // just not going to look for an option inside of an option.
373 : // Sue me.
374 :
375 0 : nsHTMLOptionElement *optElement = nsHTMLOptionElement::FromContent(aOptions);
376 0 : if (optElement) {
377 0 : nsresult rv = mOptions->InsertOptionAt(optElement, *aInsertIndex);
378 0 : NS_ENSURE_SUCCESS(rv, rv);
379 0 : (*aInsertIndex)++;
380 0 : return NS_OK;
381 : }
382 :
383 : // If it's at the top level, then we just found out there are non-options
384 : // at the top level, which will throw off the insert count
385 0 : if (aDepth == 0) {
386 0 : mNonOptionChildren++;
387 : }
388 :
389 : // Recurse down into optgroups
390 0 : if (aOptions->IsHTML(nsGkAtoms::optgroup)) {
391 0 : mOptGroupCount++;
392 :
393 0 : for (nsIContent* child = aOptions->GetFirstChild();
394 : child;
395 0 : child = child->GetNextSibling()) {
396 : nsresult rv = InsertOptionsIntoListRecurse(child,
397 0 : aInsertIndex, aDepth + 1);
398 0 : NS_ENSURE_SUCCESS(rv, rv);
399 : }
400 : }
401 :
402 0 : return NS_OK;
403 : }
404 :
405 : // If the document is such that recursing over these options gets us deeper than
406 : // four levels, there is something terribly wrong with the world.
407 : nsresult
408 0 : nsHTMLSelectElement::RemoveOptionsFromListRecurse(nsIContent* aOptions,
409 : PRInt32 aRemoveIndex,
410 : PRInt32* aNumRemoved,
411 : PRInt32 aDepth)
412 : {
413 : // We *assume* here that someone's brain has not gone horribly
414 : // wrong by putting <option> inside of <option>. I'm sorry, I'm
415 : // just not going to look for an option inside of an option.
416 : // Sue me.
417 :
418 0 : nsCOMPtr<nsIDOMHTMLOptionElement> optElement(do_QueryInterface(aOptions));
419 0 : if (optElement) {
420 0 : if (mOptions->ItemAsOption(aRemoveIndex) != optElement) {
421 0 : NS_ERROR("wrong option at index");
422 0 : return NS_ERROR_UNEXPECTED;
423 : }
424 0 : mOptions->RemoveOptionAt(aRemoveIndex);
425 0 : (*aNumRemoved)++;
426 0 : return NS_OK;
427 : }
428 :
429 : // Yay, one less artifact at the top level.
430 0 : if (aDepth == 0) {
431 0 : mNonOptionChildren--;
432 : }
433 :
434 : // Recurse down deeper for options
435 0 : if (mOptGroupCount && aOptions->IsHTML(nsGkAtoms::optgroup)) {
436 0 : mOptGroupCount--;
437 :
438 0 : for (nsIContent* child = aOptions->GetFirstChild();
439 : child;
440 0 : child = child->GetNextSibling()) {
441 : nsresult rv = RemoveOptionsFromListRecurse(child,
442 : aRemoveIndex,
443 : aNumRemoved,
444 0 : aDepth + 1);
445 0 : NS_ENSURE_SUCCESS(rv, rv);
446 : }
447 : }
448 :
449 0 : return NS_OK;
450 : }
451 :
452 : // XXXldb Doing the processing before the content nodes have been added
453 : // to the document (as the name of this function seems to require, and
454 : // as the callers do), is highly unusual. Passing around unparented
455 : // content to other parts of the app can make those things think the
456 : // options are the root content node.
457 : NS_IMETHODIMP
458 0 : nsHTMLSelectElement::WillAddOptions(nsIContent* aOptions,
459 : nsIContent* aParent,
460 : PRInt32 aContentIndex,
461 : bool aNotify)
462 : {
463 0 : PRInt32 level = GetContentDepth(aParent);
464 0 : if (level == -1) {
465 0 : return NS_ERROR_FAILURE;
466 : }
467 :
468 : // Get the index where the options will be inserted
469 0 : PRInt32 ind = -1;
470 0 : if (!mNonOptionChildren) {
471 : // If there are no artifacts, aContentIndex == ind
472 0 : ind = aContentIndex;
473 : } else {
474 : // If there are artifacts, we have to get the index of the option the
475 : // hard way
476 0 : PRInt32 children = aParent->GetChildCount();
477 :
478 0 : if (aContentIndex >= children) {
479 : // If the content insert is after the end of the parent, then we want to get
480 : // the next index *after* the parent and insert there.
481 0 : ind = GetOptionIndexAfter(aParent);
482 : } else {
483 : // If the content insert is somewhere in the middle of the container, then
484 : // we want to get the option currently at the index and insert in front of
485 : // that.
486 0 : nsIContent *currentKid = aParent->GetChildAt(aContentIndex);
487 0 : NS_ASSERTION(currentKid, "Child not found!");
488 0 : if (currentKid) {
489 0 : ind = GetOptionIndexAt(currentKid);
490 : } else {
491 0 : ind = -1;
492 : }
493 : }
494 : }
495 :
496 0 : return InsertOptionsIntoList(aOptions, ind, level, aNotify);
497 : }
498 :
499 : NS_IMETHODIMP
500 0 : nsHTMLSelectElement::WillRemoveOptions(nsIContent* aParent,
501 : PRInt32 aContentIndex,
502 : bool aNotify)
503 : {
504 0 : PRInt32 level = GetContentDepth(aParent);
505 0 : NS_ASSERTION(level >= 0, "getting notified by unexpected content");
506 0 : if (level == -1) {
507 0 : return NS_ERROR_FAILURE;
508 : }
509 :
510 : // Get the index where the options will be removed
511 0 : nsIContent *currentKid = aParent->GetChildAt(aContentIndex);
512 0 : if (currentKid) {
513 : PRInt32 ind;
514 0 : if (!mNonOptionChildren) {
515 : // If there are no artifacts, aContentIndex == ind
516 0 : ind = aContentIndex;
517 : } else {
518 : // If there are artifacts, we have to get the index of the option the
519 : // hard way
520 0 : ind = GetFirstOptionIndex(currentKid);
521 : }
522 0 : if (ind != -1) {
523 0 : nsresult rv = RemoveOptionsFromList(currentKid, ind, level, aNotify);
524 0 : NS_ENSURE_SUCCESS(rv, rv);
525 : }
526 : }
527 :
528 0 : return NS_OK;
529 : }
530 :
531 : PRInt32
532 0 : nsHTMLSelectElement::GetContentDepth(nsIContent* aContent)
533 : {
534 0 : nsIContent* content = aContent;
535 :
536 0 : PRInt32 retval = 0;
537 0 : while (content != this) {
538 0 : retval++;
539 0 : content = content->GetParent();
540 0 : if (!content) {
541 0 : retval = -1;
542 0 : break;
543 : }
544 : }
545 :
546 0 : return retval;
547 : }
548 :
549 : PRInt32
550 0 : nsHTMLSelectElement::GetOptionIndexAt(nsIContent* aOptions)
551 : {
552 : // Search this node and below.
553 : // If not found, find the first one *after* this node.
554 0 : PRInt32 retval = GetFirstOptionIndex(aOptions);
555 0 : if (retval == -1) {
556 0 : retval = GetOptionIndexAfter(aOptions);
557 : }
558 :
559 0 : return retval;
560 : }
561 :
562 : PRInt32
563 0 : nsHTMLSelectElement::GetOptionIndexAfter(nsIContent* aOptions)
564 : {
565 : // - If this is the select, the next option is the last.
566 : // - If not, search all the options after aOptions and up to the last option
567 : // in the parent.
568 : // - If it's not there, search for the first option after the parent.
569 0 : if (aOptions == this) {
570 : PRUint32 len;
571 0 : GetLength(&len);
572 0 : return len;
573 : }
574 :
575 0 : PRInt32 retval = -1;
576 :
577 0 : nsCOMPtr<nsIContent> parent = aOptions->GetParent();
578 :
579 0 : if (parent) {
580 0 : PRInt32 index = parent->IndexOf(aOptions);
581 0 : PRInt32 count = parent->GetChildCount();
582 :
583 0 : retval = GetFirstChildOptionIndex(parent, index+1, count);
584 :
585 0 : if (retval == -1) {
586 0 : retval = GetOptionIndexAfter(parent);
587 : }
588 : }
589 :
590 0 : return retval;
591 : }
592 :
593 : PRInt32
594 0 : nsHTMLSelectElement::GetFirstOptionIndex(nsIContent* aOptions)
595 : {
596 0 : PRInt32 listIndex = -1;
597 0 : nsHTMLOptionElement *optElement = nsHTMLOptionElement::FromContent(aOptions);
598 0 : if (optElement) {
599 0 : GetOptionIndex(optElement, 0, true, &listIndex);
600 : // If you nested stuff under the option, you're just plain
601 : // screwed. *I'm* not going to aid and abet your evil deed.
602 0 : return listIndex;
603 : }
604 :
605 0 : listIndex = GetFirstChildOptionIndex(aOptions, 0, aOptions->GetChildCount());
606 :
607 0 : return listIndex;
608 : }
609 :
610 : PRInt32
611 0 : nsHTMLSelectElement::GetFirstChildOptionIndex(nsIContent* aOptions,
612 : PRInt32 aStartIndex,
613 : PRInt32 aEndIndex)
614 : {
615 0 : PRInt32 retval = -1;
616 :
617 0 : for (PRInt32 i = aStartIndex; i < aEndIndex; ++i) {
618 0 : retval = GetFirstOptionIndex(aOptions->GetChildAt(i));
619 0 : if (retval != -1) {
620 0 : break;
621 : }
622 : }
623 :
624 0 : return retval;
625 : }
626 :
627 : nsISelectControlFrame *
628 0 : nsHTMLSelectElement::GetSelectFrame()
629 : {
630 0 : nsIFormControlFrame* form_control_frame = GetFormControlFrame(false);
631 :
632 0 : nsISelectControlFrame *select_frame = nsnull;
633 :
634 0 : if (form_control_frame) {
635 0 : select_frame = do_QueryFrame(form_control_frame);
636 : }
637 :
638 0 : return select_frame;
639 : }
640 :
641 : nsresult
642 0 : nsHTMLSelectElement::Add(nsIDOMHTMLElement* aElement,
643 : nsIDOMHTMLElement* aBefore)
644 : {
645 0 : nsCOMPtr<nsIDOMNode> added;
646 0 : if (!aBefore) {
647 0 : return AppendChild(aElement, getter_AddRefs(added));
648 : }
649 :
650 : // Just in case we're not the parent, get the parent of the reference
651 : // element
652 0 : nsCOMPtr<nsIDOMNode> parent;
653 0 : aBefore->GetParentNode(getter_AddRefs(parent));
654 0 : if (!parent) {
655 : // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT
656 : // element.
657 0 : return NS_ERROR_DOM_NOT_FOUND_ERR;
658 : }
659 :
660 0 : nsCOMPtr<nsIDOMNode> ancestor(parent);
661 0 : nsCOMPtr<nsIDOMNode> temp;
662 0 : while (ancestor != static_cast<nsIDOMNode*>(this)) {
663 0 : ancestor->GetParentNode(getter_AddRefs(temp));
664 0 : if (!temp) {
665 : // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT
666 : // element.
667 0 : return NS_ERROR_DOM_NOT_FOUND_ERR;
668 : }
669 0 : temp.swap(ancestor);
670 : }
671 :
672 : // If the before parameter is not null, we are equivalent to the
673 : // insertBefore method on the parent of before.
674 0 : return parent->InsertBefore(aElement, aBefore, getter_AddRefs(added));
675 : }
676 :
677 : NS_IMETHODIMP
678 0 : nsHTMLSelectElement::Add(nsIDOMHTMLElement* aElement,
679 : nsIVariant* aBefore)
680 : {
681 : PRUint16 dataType;
682 0 : nsresult rv = aBefore->GetDataType(&dataType);
683 0 : NS_ENSURE_SUCCESS(rv, rv);
684 :
685 : // aBefore is omitted, undefined or null
686 0 : if (dataType == nsIDataType::VTYPE_EMPTY ||
687 : dataType == nsIDataType::VTYPE_VOID) {
688 0 : return Add(aElement);
689 : }
690 :
691 0 : nsCOMPtr<nsISupports> supports;
692 0 : nsCOMPtr<nsIDOMHTMLElement> beforeElement;
693 :
694 : // whether aBefore is nsIDOMHTMLElement...
695 0 : if (NS_SUCCEEDED(aBefore->GetAsISupports(getter_AddRefs(supports)))) {
696 0 : beforeElement = do_QueryInterface(supports);
697 :
698 0 : NS_ENSURE_TRUE(beforeElement, NS_ERROR_DOM_SYNTAX_ERR);
699 0 : return Add(aElement, beforeElement);
700 : }
701 :
702 : // otherwise, whether aBefore is long
703 : PRInt32 index;
704 0 : NS_ENSURE_SUCCESS(aBefore->GetAsInt32(&index), NS_ERROR_DOM_SYNTAX_ERR);
705 :
706 : // If item index is out of range, insert to last.
707 : // (since beforeElement becomes null, it is inserted to last)
708 0 : nsCOMPtr<nsIDOMNode> beforeNode;
709 0 : if (NS_SUCCEEDED(Item(index, getter_AddRefs(beforeNode)))) {
710 0 : beforeElement = do_QueryInterface(beforeNode);
711 : }
712 :
713 0 : return Add(aElement, beforeElement);
714 : }
715 :
716 : NS_IMETHODIMP
717 0 : nsHTMLSelectElement::Remove(PRInt32 aIndex)
718 : {
719 0 : nsCOMPtr<nsIDOMNode> option;
720 0 : Item(aIndex, getter_AddRefs(option));
721 :
722 0 : if (option) {
723 0 : nsCOMPtr<nsIDOMNode> parent;
724 :
725 0 : option->GetParentNode(getter_AddRefs(parent));
726 0 : if (parent) {
727 0 : nsCOMPtr<nsIDOMNode> ret;
728 0 : parent->RemoveChild(option, getter_AddRefs(ret));
729 : }
730 : }
731 :
732 0 : return NS_OK;
733 : }
734 :
735 : NS_IMETHODIMP
736 0 : nsHTMLSelectElement::GetOptions(nsIDOMHTMLOptionsCollection** aValue)
737 : {
738 0 : NS_IF_ADDREF(*aValue = GetOptions());
739 :
740 0 : return NS_OK;
741 : }
742 :
743 : NS_IMETHODIMP
744 0 : nsHTMLSelectElement::GetType(nsAString& aType)
745 : {
746 0 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
747 0 : aType.AssignLiteral("select-multiple");
748 : }
749 : else {
750 0 : aType.AssignLiteral("select-one");
751 : }
752 :
753 0 : return NS_OK;
754 : }
755 :
756 : NS_IMETHODIMP
757 0 : nsHTMLSelectElement::GetLength(PRUint32* aLength)
758 : {
759 0 : return mOptions->GetLength(aLength);
760 : }
761 :
762 : #define MAX_DYNAMIC_SELECT_LENGTH 10000
763 :
764 : NS_IMETHODIMP
765 0 : nsHTMLSelectElement::SetLength(PRUint32 aLength)
766 : {
767 : PRUint32 curlen;
768 0 : nsresult rv = GetLength(&curlen);
769 0 : if (NS_FAILED(rv)) {
770 0 : curlen = 0;
771 : }
772 :
773 0 : if (curlen > aLength) { // Remove extra options
774 0 : for (PRUint32 i = curlen; i > aLength && NS_SUCCEEDED(rv); --i) {
775 0 : rv = Remove(i - 1);
776 : }
777 0 : } else if (aLength > curlen) {
778 0 : if (aLength > MAX_DYNAMIC_SELECT_LENGTH) {
779 0 : return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
780 : }
781 :
782 : // This violates the W3C DOM but we do this for backwards compatibility
783 0 : nsCOMPtr<nsINodeInfo> nodeInfo;
784 :
785 : nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::option,
786 0 : getter_AddRefs(nodeInfo));
787 :
788 0 : nsCOMPtr<nsIContent> element = NS_NewHTMLOptionElement(nodeInfo.forget());
789 0 : if (!element) {
790 0 : return NS_ERROR_OUT_OF_MEMORY;
791 : }
792 :
793 0 : nsCOMPtr<nsIContent> text;
794 0 : rv = NS_NewTextNode(getter_AddRefs(text), mNodeInfo->NodeInfoManager());
795 0 : NS_ENSURE_SUCCESS(rv, rv);
796 :
797 0 : rv = element->AppendChildTo(text, false);
798 0 : NS_ENSURE_SUCCESS(rv, rv);
799 :
800 0 : nsCOMPtr<nsIDOMNode> node(do_QueryInterface(element));
801 :
802 0 : for (PRUint32 i = curlen; i < aLength; i++) {
803 0 : nsCOMPtr<nsIDOMNode> tmpNode;
804 :
805 0 : rv = AppendChild(node, getter_AddRefs(tmpNode));
806 0 : NS_ENSURE_SUCCESS(rv, rv);
807 :
808 0 : if (i + 1 < aLength) {
809 0 : nsCOMPtr<nsIDOMNode> newNode;
810 :
811 0 : rv = node->CloneNode(true, 1, getter_AddRefs(newNode));
812 0 : NS_ENSURE_SUCCESS(rv, rv);
813 :
814 0 : node = newNode;
815 : }
816 : }
817 : }
818 :
819 0 : return NS_OK;
820 : }
821 :
822 : //NS_IMPL_INT_ATTR(nsHTMLSelectElement, SelectedIndex, selectedindex)
823 :
824 : NS_IMETHODIMP
825 0 : nsHTMLSelectElement::GetSelectedIndex(PRInt32* aValue)
826 : {
827 0 : *aValue = mSelectedIndex;
828 :
829 0 : return NS_OK;
830 : }
831 :
832 : nsresult
833 0 : nsHTMLSelectElement::SetSelectedIndexInternal(PRInt32 aIndex, bool aNotify)
834 : {
835 0 : PRInt32 oldSelectedIndex = mSelectedIndex;
836 :
837 : nsresult rv = SetOptionsSelectedByIndex(aIndex, aIndex, true,
838 0 : true, true, aNotify, nsnull);
839 :
840 0 : if (NS_SUCCEEDED(rv)) {
841 0 : nsISelectControlFrame* selectFrame = GetSelectFrame();
842 0 : if (selectFrame) {
843 0 : rv = selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex);
844 : }
845 : }
846 :
847 0 : SetSelectionChanged(true, aNotify);
848 :
849 0 : return rv;
850 : }
851 :
852 : NS_IMETHODIMP
853 0 : nsHTMLSelectElement::SetSelectedIndex(PRInt32 aIndex)
854 : {
855 0 : return SetSelectedIndexInternal(aIndex, true);
856 : }
857 :
858 : NS_IMETHODIMP
859 0 : nsHTMLSelectElement::GetOptionIndex(nsIDOMHTMLOptionElement* aOption,
860 : PRInt32 aStartIndex, bool aForward,
861 : PRInt32* aIndex)
862 : {
863 0 : nsCOMPtr<nsINode> option = do_QueryInterface(aOption);
864 0 : return mOptions->GetOptionIndex(option->AsElement(), aStartIndex, aForward, aIndex);
865 : }
866 :
867 : bool
868 0 : nsHTMLSelectElement::IsOptionSelectedByIndex(PRInt32 aIndex)
869 : {
870 0 : nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(aIndex);
871 0 : bool isSelected = false;
872 0 : if (option) {
873 0 : option->GetSelected(&isSelected);
874 : }
875 0 : return isSelected;
876 : }
877 :
878 : void
879 0 : nsHTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame,
880 : PRInt32 aIndex,
881 : bool aSelected,
882 : bool aChangeOptionState,
883 : bool aNotify)
884 : {
885 : // Set the selected index
886 0 : if (aSelected && (aIndex < mSelectedIndex || mSelectedIndex < 0)) {
887 0 : mSelectedIndex = aIndex;
888 0 : SetSelectionChanged(true, aNotify);
889 0 : } else if (!aSelected && aIndex == mSelectedIndex) {
890 0 : FindSelectedIndex(aIndex + 1, aNotify);
891 : }
892 :
893 0 : if (aChangeOptionState) {
894 : // Tell the option to get its bad self selected
895 0 : nsCOMPtr<nsIDOMNode> option;
896 0 : Item(aIndex, getter_AddRefs(option));
897 0 : if (option) {
898 : nsRefPtr<nsHTMLOptionElement> optionElement =
899 0 : static_cast<nsHTMLOptionElement*>(option.get());
900 0 : optionElement->SetSelectedInternal(aSelected, aNotify);
901 : }
902 : }
903 :
904 : // Let the frame know too
905 0 : if (aSelectFrame) {
906 0 : aSelectFrame->OnOptionSelected(aIndex, aSelected);
907 : }
908 :
909 0 : UpdateValueMissingValidityState();
910 0 : UpdateState(aNotify);
911 0 : }
912 :
913 : void
914 0 : nsHTMLSelectElement::FindSelectedIndex(PRInt32 aStartIndex, bool aNotify)
915 : {
916 0 : mSelectedIndex = -1;
917 0 : SetSelectionChanged(true, aNotify);
918 : PRUint32 len;
919 0 : GetLength(&len);
920 0 : for (PRInt32 i = aStartIndex; i < PRInt32(len); i++) {
921 0 : if (IsOptionSelectedByIndex(i)) {
922 0 : mSelectedIndex = i;
923 0 : SetSelectionChanged(true, aNotify);
924 0 : break;
925 : }
926 : }
927 0 : }
928 :
929 : // XXX Consider splitting this into two functions for ease of reading:
930 : // SelectOptionsByIndex(startIndex, endIndex, clearAll, checkDisabled)
931 : // startIndex, endIndex - the range of options to turn on
932 : // (-1, -1) will clear all indices no matter what.
933 : // clearAll - will clear all other options unless checkDisabled is on
934 : // and all the options attempted to be set are disabled
935 : // (note that if it is not multiple, and an option is selected,
936 : // everything else will be cleared regardless).
937 : // checkDisabled - if this is TRUE, and an option is disabled, it will not be
938 : // changed regardless of whether it is selected or not.
939 : // Generally the UI passes TRUE and JS passes FALSE.
940 : // (setDisabled currently is the opposite)
941 : // DeselectOptionsByIndex(startIndex, endIndex, checkDisabled)
942 : // startIndex, endIndex - the range of options to turn on
943 : // (-1, -1) will clear all indices no matter what.
944 : // checkDisabled - if this is TRUE, and an option is disabled, it will not be
945 : // changed regardless of whether it is selected or not.
946 : // Generally the UI passes TRUE and JS passes FALSE.
947 : // (setDisabled currently is the opposite)
948 : //
949 : // XXXbz the above comment is pretty confusing. Maybe we should actually
950 : // document the args to this function too, in addition to documenting what
951 : // things might end up looking like? In particular, pay attention to the
952 : // setDisabled vs checkDisabled business.
953 : NS_IMETHODIMP
954 0 : nsHTMLSelectElement::SetOptionsSelectedByIndex(PRInt32 aStartIndex,
955 : PRInt32 aEndIndex,
956 : bool aIsSelected,
957 : bool aClearAll,
958 : bool aSetDisabled,
959 : bool aNotify,
960 : bool* aChangedSomething)
961 : {
962 : #if 0
963 : printf("SetOption(%d-%d, %c, ClearAll=%c)\n", aStartIndex, aEndIndex,
964 : (aIsSelected ? 'Y' : 'N'),
965 : (aClearAll ? 'Y' : 'N'));
966 : #endif
967 0 : if (aChangedSomething) {
968 0 : *aChangedSomething = false;
969 : }
970 :
971 : // Don't bother if the select is disabled
972 0 : if (!aSetDisabled && IsDisabled()) {
973 0 : return NS_OK;
974 : }
975 :
976 : // Don't bother if there are no options
977 0 : PRUint32 numItems = 0;
978 0 : GetLength(&numItems);
979 0 : if (numItems == 0) {
980 0 : return NS_OK;
981 : }
982 :
983 : // First, find out whether multiple items can be selected
984 0 : bool isMultiple = HasAttr(kNameSpaceID_None, nsGkAtoms::multiple);
985 :
986 : // These variables tell us whether any options were selected
987 : // or deselected.
988 0 : bool optionsSelected = false;
989 0 : bool optionsDeselected = false;
990 :
991 0 : nsISelectControlFrame *selectFrame = nsnull;
992 0 : bool didGetFrame = false;
993 0 : nsWeakFrame weakSelectFrame;
994 :
995 0 : if (aIsSelected) {
996 : // Setting selectedIndex to an out-of-bounds index means -1. (HTML5)
997 0 : if (aStartIndex >= (PRInt32)numItems || aStartIndex < 0 ||
998 : aEndIndex >= (PRInt32)numItems || aEndIndex < 0) {
999 0 : aStartIndex = -1;
1000 0 : aEndIndex = -1;
1001 : }
1002 :
1003 : // Only select the first value if it's not multiple
1004 0 : if (!isMultiple) {
1005 0 : aEndIndex = aStartIndex;
1006 : }
1007 :
1008 : // This variable tells whether or not all of the options we attempted to
1009 : // select are disabled. If ClearAll is passed in as true, and we do not
1010 : // select anything because the options are disabled, we will not clear the
1011 : // other options. (This is to make the UI work the way one might expect.)
1012 0 : bool allDisabled = !aSetDisabled;
1013 :
1014 : //
1015 : // Save a little time when clearing other options
1016 : //
1017 0 : PRInt32 previousSelectedIndex = mSelectedIndex;
1018 :
1019 : //
1020 : // Select the requested indices
1021 : //
1022 : // If index is -1, everything will be deselected (bug 28143)
1023 0 : if (aStartIndex != -1) {
1024 : // Loop through the options and select them (if they are not disabled and
1025 : // if they are not already selected).
1026 0 : for (PRInt32 optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) {
1027 :
1028 : // Ignore disabled options.
1029 0 : if (!aSetDisabled) {
1030 : bool isDisabled;
1031 0 : IsOptionDisabled(optIndex, &isDisabled);
1032 :
1033 0 : if (isDisabled) {
1034 0 : continue;
1035 : } else {
1036 0 : allDisabled = false;
1037 : }
1038 : }
1039 :
1040 0 : nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
1041 0 : if (option) {
1042 : // If the index is already selected, ignore it.
1043 0 : bool isSelected = false;
1044 0 : option->GetSelected(&isSelected);
1045 0 : if (!isSelected) {
1046 : // To notify the frame if anything gets changed. No need
1047 : // to flush here, if there's no frame yet we don't need to
1048 : // force it to be created just to notify it about a change
1049 : // in the select.
1050 0 : selectFrame = GetSelectFrame();
1051 0 : weakSelectFrame = do_QueryFrame(selectFrame);
1052 0 : didGetFrame = true;
1053 :
1054 0 : OnOptionSelected(selectFrame, optIndex, true, true, aNotify);
1055 0 : optionsSelected = true;
1056 : }
1057 : }
1058 : }
1059 : }
1060 :
1061 : // Next remove all other options if single select or all is clear
1062 : // If index is -1, everything will be deselected (bug 28143)
1063 0 : if (((!isMultiple && optionsSelected)
1064 0 : || (aClearAll && !allDisabled)
1065 : || aStartIndex == -1)
1066 : && previousSelectedIndex != -1) {
1067 0 : for (PRInt32 optIndex = previousSelectedIndex;
1068 : optIndex < PRInt32(numItems);
1069 : optIndex++) {
1070 0 : if (optIndex < aStartIndex || optIndex > aEndIndex) {
1071 0 : nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
1072 0 : if (option) {
1073 : // If the index is already selected, ignore it.
1074 0 : bool isSelected = false;
1075 0 : option->GetSelected(&isSelected);
1076 0 : if (isSelected) {
1077 0 : if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
1078 : // To notify the frame if anything gets changed, don't
1079 : // flush, if the frame doesn't exist we don't need to
1080 : // create it just to tell it about this change.
1081 0 : selectFrame = GetSelectFrame();
1082 0 : weakSelectFrame = do_QueryFrame(selectFrame);
1083 :
1084 0 : didGetFrame = true;
1085 : }
1086 :
1087 : OnOptionSelected(selectFrame, optIndex, false, true,
1088 0 : aNotify);
1089 0 : optionsDeselected = true;
1090 :
1091 : // Only need to deselect one option if not multiple
1092 0 : if (!isMultiple) {
1093 0 : break;
1094 : }
1095 : }
1096 : }
1097 : }
1098 : }
1099 : }
1100 :
1101 : } else {
1102 :
1103 : // If we're deselecting, loop through all selected items and deselect
1104 : // any that are in the specified range.
1105 0 : for (PRInt32 optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) {
1106 0 : if (!aSetDisabled) {
1107 : bool isDisabled;
1108 0 : IsOptionDisabled(optIndex, &isDisabled);
1109 0 : if (isDisabled) {
1110 0 : continue;
1111 : }
1112 : }
1113 :
1114 0 : nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
1115 0 : if (option) {
1116 : // If the index is already selected, ignore it.
1117 0 : bool isSelected = false;
1118 0 : option->GetSelected(&isSelected);
1119 0 : if (isSelected) {
1120 0 : if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) {
1121 : // To notify the frame if anything gets changed, don't
1122 : // flush, if the frame doesn't exist we don't need to
1123 : // create it just to tell it about this change.
1124 0 : selectFrame = GetSelectFrame();
1125 0 : weakSelectFrame = do_QueryFrame(selectFrame);
1126 :
1127 0 : didGetFrame = true;
1128 : }
1129 :
1130 0 : OnOptionSelected(selectFrame, optIndex, false, true, aNotify);
1131 0 : optionsDeselected = true;
1132 : }
1133 : }
1134 : }
1135 : }
1136 :
1137 : // Make sure something is selected unless we were set to -1 (none)
1138 0 : if (optionsDeselected && aStartIndex != -1) {
1139 0 : optionsSelected = CheckSelectSomething(aNotify) || optionsSelected;
1140 : }
1141 :
1142 : // Let the caller know whether anything was changed
1143 0 : if (optionsSelected || optionsDeselected) {
1144 0 : if (aChangedSomething)
1145 0 : *aChangedSomething = true;
1146 : }
1147 :
1148 0 : return NS_OK;
1149 : }
1150 :
1151 : NS_IMETHODIMP
1152 0 : nsHTMLSelectElement::IsOptionDisabled(PRInt32 aIndex, bool* aIsDisabled)
1153 : {
1154 0 : *aIsDisabled = false;
1155 0 : nsCOMPtr<nsIDOMNode> optionNode;
1156 0 : Item(aIndex, getter_AddRefs(optionNode));
1157 0 : NS_ENSURE_TRUE(optionNode, NS_ERROR_FAILURE);
1158 :
1159 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(optionNode);
1160 0 : if (option) {
1161 : bool isDisabled;
1162 0 : option->GetDisabled(&isDisabled);
1163 0 : if (isDisabled) {
1164 0 : *aIsDisabled = true;
1165 0 : return NS_OK;
1166 : }
1167 : }
1168 :
1169 : // Check for disabled optgroups
1170 : // If there are no artifacts, there are no optgroups
1171 0 : if (mNonOptionChildren) {
1172 0 : nsCOMPtr<nsIDOMNode> parent;
1173 0 : while (1) {
1174 0 : optionNode->GetParentNode(getter_AddRefs(parent));
1175 :
1176 : // If we reached the top of the doc (scary), we're done
1177 0 : if (!parent) {
1178 0 : break;
1179 : }
1180 :
1181 : // If we reached the select element, we're done
1182 : nsCOMPtr<nsIDOMHTMLSelectElement> selectElement =
1183 0 : do_QueryInterface(parent);
1184 0 : if (selectElement) {
1185 : break;
1186 : }
1187 :
1188 : nsCOMPtr<nsIDOMHTMLOptGroupElement> optGroupElement =
1189 0 : do_QueryInterface(parent);
1190 :
1191 0 : if (optGroupElement) {
1192 : bool isDisabled;
1193 0 : optGroupElement->GetDisabled(&isDisabled);
1194 :
1195 0 : if (isDisabled) {
1196 0 : *aIsDisabled = true;
1197 0 : return NS_OK;
1198 : }
1199 : } else {
1200 : // If you put something else between you and the optgroup, you're a
1201 : // moron and you deserve not to have optgroup disabling work.
1202 : break;
1203 : }
1204 :
1205 0 : optionNode = parent;
1206 : }
1207 : }
1208 :
1209 0 : return NS_OK;
1210 : }
1211 :
1212 : NS_IMETHODIMP
1213 0 : nsHTMLSelectElement::GetValue(nsAString& aValue)
1214 : {
1215 : PRInt32 selectedIndex;
1216 :
1217 0 : nsresult rv = GetSelectedIndex(&selectedIndex);
1218 :
1219 0 : if (NS_SUCCEEDED(rv) && selectedIndex > -1) {
1220 0 : nsCOMPtr<nsIDOMNode> node;
1221 :
1222 0 : rv = Item(selectedIndex, getter_AddRefs(node));
1223 :
1224 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(node);
1225 0 : if (NS_SUCCEEDED(rv) && option) {
1226 0 : return option->GetValue(aValue);
1227 : }
1228 : }
1229 :
1230 0 : aValue.Truncate();
1231 0 : return rv;
1232 : }
1233 :
1234 : NS_IMETHODIMP
1235 0 : nsHTMLSelectElement::SetValue(const nsAString& aValue)
1236 : {
1237 : PRUint32 length;
1238 0 : nsresult rv = GetLength(&length);
1239 0 : NS_ENSURE_SUCCESS(rv, rv);
1240 :
1241 0 : for (PRUint32 i = 0; i < length; i++) {
1242 0 : nsCOMPtr<nsIDOMNode> node;
1243 0 : rv = Item(i, getter_AddRefs(node));
1244 0 : if (NS_FAILED(rv) || !node) {
1245 0 : continue;
1246 : }
1247 :
1248 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(node);
1249 0 : if (!option) {
1250 0 : continue;
1251 : }
1252 0 : nsAutoString optionVal;
1253 0 : option->GetValue(optionVal);
1254 0 : if (optionVal.Equals(aValue)) {
1255 0 : SetSelectedIndexInternal(PRInt32(i), true);
1256 : break;
1257 : }
1258 : }
1259 0 : return rv;
1260 : }
1261 :
1262 :
1263 0 : NS_IMPL_BOOL_ATTR(nsHTMLSelectElement, Autofocus, autofocus)
1264 0 : NS_IMPL_BOOL_ATTR(nsHTMLSelectElement, Disabled, disabled)
1265 0 : NS_IMPL_BOOL_ATTR(nsHTMLSelectElement, Multiple, multiple)
1266 0 : NS_IMPL_STRING_ATTR(nsHTMLSelectElement, Name, name)
1267 0 : NS_IMPL_BOOL_ATTR(nsHTMLSelectElement, Required, required)
1268 0 : NS_IMPL_NON_NEGATIVE_INT_ATTR_DEFAULT_VALUE(nsHTMLSelectElement, Size, size, 0)
1269 0 : NS_IMPL_INT_ATTR(nsHTMLSelectElement, TabIndex, tabindex)
1270 :
1271 : bool
1272 0 : nsHTMLSelectElement::IsHTMLFocusable(bool aWithMouse,
1273 : bool *aIsFocusable, PRInt32 *aTabIndex)
1274 : {
1275 0 : if (nsGenericHTMLFormElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex)) {
1276 0 : return true;
1277 : }
1278 :
1279 0 : *aIsFocusable = !IsDisabled();
1280 :
1281 0 : return false;
1282 : }
1283 :
1284 : NS_IMETHODIMP
1285 0 : nsHTMLSelectElement::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
1286 : {
1287 0 : return mOptions->Item(aIndex, aReturn);
1288 : }
1289 :
1290 : NS_IMETHODIMP
1291 0 : nsHTMLSelectElement::NamedItem(const nsAString& aName,
1292 : nsIDOMNode** aReturn)
1293 : {
1294 0 : return mOptions->NamedItem(aName, aReturn);
1295 : }
1296 :
1297 : bool
1298 0 : nsHTMLSelectElement::CheckSelectSomething(bool aNotify)
1299 : {
1300 0 : if (mIsDoneAddingChildren) {
1301 0 : if (mSelectedIndex < 0 && IsCombobox()) {
1302 0 : return SelectSomething(aNotify);
1303 : }
1304 : }
1305 0 : return false;
1306 : }
1307 :
1308 : bool
1309 0 : nsHTMLSelectElement::SelectSomething(bool aNotify)
1310 : {
1311 : // If we're not done building the select, don't play with this yet.
1312 0 : if (!mIsDoneAddingChildren) {
1313 0 : return false;
1314 : }
1315 :
1316 : PRUint32 count;
1317 0 : GetLength(&count);
1318 0 : for (PRUint32 i = 0; i < count; i++) {
1319 : bool disabled;
1320 0 : nsresult rv = IsOptionDisabled(i, &disabled);
1321 :
1322 0 : if (NS_FAILED(rv) || !disabled) {
1323 0 : rv = SetSelectedIndexInternal(i, aNotify);
1324 0 : NS_ENSURE_SUCCESS(rv, false);
1325 :
1326 0 : UpdateValueMissingValidityState();
1327 0 : UpdateState(aNotify);
1328 :
1329 0 : return true;
1330 : }
1331 : }
1332 :
1333 0 : return false;
1334 : }
1335 :
1336 : nsresult
1337 0 : nsHTMLSelectElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
1338 : nsIContent* aBindingParent,
1339 : bool aCompileEventHandlers)
1340 : {
1341 : nsresult rv = nsGenericHTMLFormElement::BindToTree(aDocument, aParent,
1342 : aBindingParent,
1343 0 : aCompileEventHandlers);
1344 0 : NS_ENSURE_SUCCESS(rv, rv);
1345 :
1346 : // If there is a disabled fieldset in the parent chain, the element is now
1347 : // barred from constraint validation.
1348 : // XXXbz is this still needed now that fieldset changes always call
1349 : // FieldSetDisabledChanged?
1350 0 : UpdateBarredFromConstraintValidation();
1351 :
1352 : // And now make sure our state is up to date
1353 0 : UpdateState(false);
1354 :
1355 0 : return rv;
1356 : }
1357 :
1358 : void
1359 0 : nsHTMLSelectElement::UnbindFromTree(bool aDeep, bool aNullParent)
1360 : {
1361 0 : nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent);
1362 :
1363 : // We might be no longer disabled because our parent chain changed.
1364 : // XXXbz is this still needed now that fieldset changes always call
1365 : // FieldSetDisabledChanged?
1366 0 : UpdateBarredFromConstraintValidation();
1367 :
1368 : // And now make sure our state is up to date
1369 0 : UpdateState(false);
1370 0 : }
1371 :
1372 : nsresult
1373 0 : nsHTMLSelectElement::BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
1374 : const nsAttrValueOrString* aValue,
1375 : bool aNotify)
1376 : {
1377 0 : if (aNotify && aName == nsGkAtoms::disabled &&
1378 : aNameSpaceID == kNameSpaceID_None) {
1379 0 : mDisabledChanged = true;
1380 : }
1381 :
1382 : return nsGenericHTMLFormElement::BeforeSetAttr(aNameSpaceID, aName,
1383 0 : aValue, aNotify);
1384 : }
1385 :
1386 : nsresult
1387 0 : nsHTMLSelectElement::AfterSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
1388 : const nsAttrValue* aValue, bool aNotify)
1389 : {
1390 0 : if (aNameSpaceID == kNameSpaceID_None) {
1391 0 : if (aName == nsGkAtoms::disabled) {
1392 0 : UpdateBarredFromConstraintValidation();
1393 0 : } else if (aName == nsGkAtoms::required) {
1394 0 : UpdateValueMissingValidityState();
1395 : }
1396 :
1397 0 : UpdateState(aNotify);
1398 : }
1399 :
1400 : return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName,
1401 0 : aValue, aNotify);
1402 : }
1403 :
1404 : nsresult
1405 0 : nsHTMLSelectElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute,
1406 : bool aNotify)
1407 : {
1408 0 : if (aNotify && aNameSpaceID == kNameSpaceID_None &&
1409 : aAttribute == nsGkAtoms::multiple) {
1410 : // We're changing from being a multi-select to a single-select.
1411 : // Make sure we only have one option selected before we do that.
1412 : // Note that this needs to come before we really unset the attr,
1413 : // since SetOptionsSelectedByIndex does some bail-out type
1414 : // optimization for cases when the select is not multiple that
1415 : // would lead to only a single option getting deselected.
1416 0 : if (mSelectedIndex >= 0) {
1417 0 : SetSelectedIndexInternal(mSelectedIndex, aNotify);
1418 : }
1419 : }
1420 :
1421 : nsresult rv = nsGenericHTMLFormElement::UnsetAttr(aNameSpaceID, aAttribute,
1422 0 : aNotify);
1423 0 : NS_ENSURE_SUCCESS(rv, rv);
1424 :
1425 0 : if (aNotify && aNameSpaceID == kNameSpaceID_None &&
1426 : aAttribute == nsGkAtoms::multiple) {
1427 : // We might have become a combobox; make sure _something_ gets
1428 : // selected in that case
1429 0 : CheckSelectSomething(aNotify);
1430 : }
1431 :
1432 0 : return rv;
1433 : }
1434 :
1435 : void
1436 0 : nsHTMLSelectElement::DoneAddingChildren(bool aHaveNotified)
1437 : {
1438 0 : mIsDoneAddingChildren = true;
1439 :
1440 0 : nsISelectControlFrame* selectFrame = GetSelectFrame();
1441 :
1442 : // If we foolishly tried to restore before we were done adding
1443 : // content, restore the rest of the options proper-like
1444 0 : if (mRestoreState) {
1445 0 : RestoreStateTo(mRestoreState);
1446 0 : mRestoreState = nsnull;
1447 : }
1448 :
1449 : // Notify the frame
1450 0 : if (selectFrame) {
1451 0 : selectFrame->DoneAddingChildren(true);
1452 : }
1453 :
1454 : // Restore state
1455 0 : if (!mInhibitStateRestoration) {
1456 0 : RestoreFormControlState(this, this);
1457 : }
1458 :
1459 : // Now that we're done, select something (if it's a single select something
1460 : // must be selected)
1461 0 : if (!CheckSelectSomething(false)) {
1462 : // If an option has @selected set, it will be selected during parsing but
1463 : // with an empty value. We have to make sure the select element updates it's
1464 : // validity state to take this into account.
1465 0 : UpdateValueMissingValidityState();
1466 :
1467 : // And now make sure we update our content state too
1468 0 : UpdateState(aHaveNotified);
1469 : }
1470 :
1471 0 : mDefaultSelectionSet = true;
1472 0 : }
1473 :
1474 : bool
1475 0 : nsHTMLSelectElement::ParseAttribute(PRInt32 aNamespaceID,
1476 : nsIAtom* aAttribute,
1477 : const nsAString& aValue,
1478 : nsAttrValue& aResult)
1479 : {
1480 0 : if (aAttribute == nsGkAtoms::size && kNameSpaceID_None == aNamespaceID) {
1481 0 : return aResult.ParsePositiveIntValue(aValue);
1482 : }
1483 : return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
1484 0 : aResult);
1485 : }
1486 :
1487 : static void
1488 0 : MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
1489 : nsRuleData* aData)
1490 : {
1491 0 : nsGenericHTMLFormElement::MapImageAlignAttributeInto(aAttributes, aData);
1492 0 : nsGenericHTMLFormElement::MapCommonAttributesInto(aAttributes, aData);
1493 0 : }
1494 :
1495 : nsChangeHint
1496 0 : nsHTMLSelectElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
1497 : PRInt32 aModType) const
1498 : {
1499 : nsChangeHint retval =
1500 0 : nsGenericHTMLFormElement::GetAttributeChangeHint(aAttribute, aModType);
1501 0 : if (aAttribute == nsGkAtoms::multiple ||
1502 : aAttribute == nsGkAtoms::size) {
1503 0 : NS_UpdateHint(retval, NS_STYLE_HINT_FRAMECHANGE);
1504 : }
1505 0 : return retval;
1506 : }
1507 :
1508 : NS_IMETHODIMP_(bool)
1509 0 : nsHTMLSelectElement::IsAttributeMapped(const nsIAtom* aAttribute) const
1510 : {
1511 : static const MappedAttributeEntry* const map[] = {
1512 : sCommonAttributeMap,
1513 : sImageAlignAttributeMap
1514 : };
1515 :
1516 0 : return FindAttributeDependence(aAttribute, map);
1517 : }
1518 :
1519 : nsMapRuleToAttributesFunc
1520 0 : nsHTMLSelectElement::GetAttributeMappingFunction() const
1521 : {
1522 0 : return &MapAttributesIntoRule;
1523 : }
1524 :
1525 :
1526 : nsresult
1527 0 : nsHTMLSelectElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
1528 : {
1529 0 : nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
1530 0 : nsIFrame* formFrame = nsnull;
1531 0 : if (formControlFrame) {
1532 0 : formFrame = do_QueryFrame(formControlFrame);
1533 : }
1534 :
1535 0 : aVisitor.mCanHandle = false;
1536 0 : if (IsElementDisabledForEvents(aVisitor.mEvent->message, formFrame)) {
1537 0 : return NS_OK;
1538 : }
1539 :
1540 0 : return nsGenericHTMLFormElement::PreHandleEvent(aVisitor);
1541 : }
1542 :
1543 : nsresult
1544 0 : nsHTMLSelectElement::PostHandleEvent(nsEventChainPostVisitor& aVisitor)
1545 : {
1546 0 : if (aVisitor.mEvent->message == NS_FOCUS_CONTENT) {
1547 : // If the invalid UI is shown, we should show it while focused and
1548 : // update the invalid/valid UI.
1549 0 : mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
1550 :
1551 : // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
1552 : // UI while focused.
1553 0 : mCanShowValidUI = ShouldShowValidityUI();
1554 :
1555 : // We don't have to update NS_EVENT_STATE_MOZ_UI_INVALID nor
1556 : // NS_EVENT_STATE_MOZ_UI_VALID given that the states should not change.
1557 0 : } else if (aVisitor.mEvent->message == NS_BLUR_CONTENT) {
1558 0 : mCanShowInvalidUI = true;
1559 0 : mCanShowValidUI = true;
1560 :
1561 0 : UpdateState(true);
1562 : }
1563 :
1564 0 : return nsGenericHTMLFormElement::PostHandleEvent(aVisitor);
1565 : }
1566 :
1567 : nsEventStates
1568 0 : nsHTMLSelectElement::IntrinsicState() const
1569 : {
1570 0 : nsEventStates state = nsGenericHTMLFormElement::IntrinsicState();
1571 :
1572 0 : if (IsCandidateForConstraintValidation()) {
1573 0 : if (IsValid()) {
1574 0 : state |= NS_EVENT_STATE_VALID;
1575 : } else {
1576 0 : state |= NS_EVENT_STATE_INVALID;
1577 :
1578 0 : if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
1579 0 : (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
1580 0 : (mCanShowInvalidUI && ShouldShowValidityUI()))) {
1581 0 : state |= NS_EVENT_STATE_MOZ_UI_INVALID;
1582 : }
1583 : }
1584 :
1585 : // :-moz-ui-valid applies if all the following are true:
1586 : // 1. The element is not focused, or had either :-moz-ui-valid or
1587 : // :-moz-ui-invalid applying before it was focused ;
1588 : // 2. The element is either valid or isn't allowed to have
1589 : // :-moz-ui-invalid applying ;
1590 : // 3. The element has no form owner or its form owner doesn't have the
1591 : // novalidate attribute set ;
1592 : // 4. The element has already been modified or the user tried to submit the
1593 : // form owner while invalid.
1594 0 : if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
1595 0 : (mCanShowValidUI && ShouldShowValidityUI() &&
1596 0 : (IsValid() || (state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
1597 0 : !mCanShowInvalidUI)))) {
1598 0 : state |= NS_EVENT_STATE_MOZ_UI_VALID;
1599 : }
1600 : }
1601 :
1602 0 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
1603 0 : state |= NS_EVENT_STATE_REQUIRED;
1604 : } else {
1605 0 : state |= NS_EVENT_STATE_OPTIONAL;
1606 : }
1607 :
1608 : return state;
1609 : }
1610 :
1611 : // nsIFormControl
1612 :
1613 : NS_IMETHODIMP
1614 0 : nsHTMLSelectElement::SaveState()
1615 : {
1616 0 : nsRefPtr<nsSelectState> state = new nsSelectState();
1617 :
1618 : PRUint32 len;
1619 0 : GetLength(&len);
1620 :
1621 0 : for (PRUint32 optIndex = 0; optIndex < len; optIndex++) {
1622 0 : nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
1623 0 : if (option) {
1624 : bool isSelected;
1625 0 : option->GetSelected(&isSelected);
1626 0 : if (isSelected) {
1627 0 : nsAutoString value;
1628 0 : option->GetValue(value);
1629 0 : state->PutOption(optIndex, value);
1630 : }
1631 : }
1632 : }
1633 :
1634 0 : nsPresState *presState = nsnull;
1635 0 : nsresult rv = GetPrimaryPresState(this, &presState);
1636 0 : if (presState) {
1637 0 : presState->SetStateProperty(state);
1638 :
1639 0 : if (mDisabledChanged) {
1640 : // We do not want to save the real disabled state but the disabled
1641 : // attribute.
1642 0 : presState->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
1643 : }
1644 : }
1645 :
1646 0 : return rv;
1647 : }
1648 :
1649 : bool
1650 0 : nsHTMLSelectElement::RestoreState(nsPresState* aState)
1651 : {
1652 : // Get the presentation state object to retrieve our stuff out of.
1653 : nsCOMPtr<nsSelectState> state(
1654 0 : do_QueryInterface(aState->GetStateProperty()));
1655 :
1656 0 : if (state) {
1657 0 : RestoreStateTo(state);
1658 :
1659 : // Don't flush, if the frame doesn't exist yet it doesn't care if
1660 : // we're reset or not.
1661 0 : DispatchContentReset();
1662 : }
1663 :
1664 0 : if (aState->IsDisabledSet()) {
1665 0 : SetDisabled(aState->GetDisabled());
1666 : }
1667 :
1668 0 : return false;
1669 : }
1670 :
1671 : void
1672 0 : nsHTMLSelectElement::RestoreStateTo(nsSelectState* aNewSelected)
1673 : {
1674 0 : if (!mIsDoneAddingChildren) {
1675 0 : mRestoreState = aNewSelected;
1676 0 : return;
1677 : }
1678 :
1679 : PRUint32 len;
1680 0 : GetLength(&len);
1681 :
1682 : // First clear all
1683 0 : SetOptionsSelectedByIndex(-1, -1, true, true, true, true, nsnull);
1684 :
1685 : // Next set the proper ones
1686 0 : for (PRInt32 i = 0; i < PRInt32(len); i++) {
1687 0 : nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(i);
1688 0 : if (option) {
1689 0 : nsAutoString value;
1690 0 : nsresult rv = option->GetValue(value);
1691 0 : if (NS_SUCCEEDED(rv) && aNewSelected->ContainsOption(i, value)) {
1692 0 : SetOptionsSelectedByIndex(i, i, true, false, true, true, nsnull);
1693 : }
1694 : }
1695 : }
1696 : }
1697 :
1698 : NS_IMETHODIMP
1699 0 : nsHTMLSelectElement::Reset()
1700 : {
1701 0 : PRUint32 numSelected = 0;
1702 :
1703 : //
1704 : // Cycle through the options array and reset the options
1705 : //
1706 : PRUint32 numOptions;
1707 0 : nsresult rv = GetLength(&numOptions);
1708 0 : NS_ENSURE_SUCCESS(rv, rv);
1709 :
1710 0 : for (PRUint32 i = 0; i < numOptions; i++) {
1711 0 : nsCOMPtr<nsIDOMNode> node;
1712 0 : rv = Item(i, getter_AddRefs(node));
1713 0 : NS_ENSURE_SUCCESS(rv, rv);
1714 :
1715 0 : nsCOMPtr<nsIDOMHTMLOptionElement> option(do_QueryInterface(node));
1716 :
1717 0 : NS_ASSERTION(option, "option not an OptionElement");
1718 0 : if (option) {
1719 : //
1720 : // Reset the option to its default value
1721 : //
1722 0 : bool selected = false;
1723 0 : option->GetDefaultSelected(&selected);
1724 : SetOptionsSelectedByIndex(i, i, selected,
1725 0 : false, true, true, nsnull);
1726 0 : if (selected) {
1727 0 : numSelected++;
1728 : }
1729 : }
1730 : }
1731 :
1732 : //
1733 : // If nothing was selected and it's not multiple, select something
1734 : //
1735 0 : if (numSelected == 0 && IsCombobox()) {
1736 0 : SelectSomething(true);
1737 : }
1738 :
1739 0 : SetSelectionChanged(false, true);
1740 :
1741 : //
1742 : // Let the frame know we were reset
1743 : //
1744 : // Don't flush, if there's no frame yet it won't care about us being
1745 : // reset even if we forced it to be created now.
1746 : //
1747 0 : DispatchContentReset();
1748 :
1749 0 : return NS_OK;
1750 : }
1751 :
1752 : static NS_DEFINE_CID(kFormProcessorCID, NS_FORMPROCESSOR_CID);
1753 :
1754 : NS_IMETHODIMP
1755 0 : nsHTMLSelectElement::SubmitNamesValues(nsFormSubmission* aFormSubmission)
1756 : {
1757 : // Disabled elements don't submit
1758 0 : if (IsDisabled()) {
1759 0 : return NS_OK;
1760 : }
1761 :
1762 : //
1763 : // Get the name (if no name, no submit)
1764 : //
1765 0 : nsAutoString name;
1766 0 : GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
1767 0 : if (name.IsEmpty()) {
1768 0 : return NS_OK;
1769 : }
1770 :
1771 : //
1772 : // Submit
1773 : //
1774 : PRUint32 len;
1775 0 : GetLength(&len);
1776 :
1777 0 : nsAutoString mozType;
1778 0 : nsCOMPtr<nsIFormProcessor> keyGenProcessor;
1779 0 : if (GetAttr(kNameSpaceID_None, nsGkAtoms::_moz_type, mozType) &&
1780 0 : mozType.EqualsLiteral("-mozilla-keygen")) {
1781 0 : keyGenProcessor = do_GetService(kFormProcessorCID);
1782 : }
1783 :
1784 0 : for (PRUint32 optIndex = 0; optIndex < len; optIndex++) {
1785 : // Don't send disabled options
1786 : bool disabled;
1787 0 : nsresult rv = IsOptionDisabled(optIndex, &disabled);
1788 0 : if (NS_FAILED(rv) || disabled) {
1789 0 : continue;
1790 : }
1791 :
1792 0 : nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
1793 0 : NS_ENSURE_TRUE(option, NS_ERROR_UNEXPECTED);
1794 :
1795 : bool isSelected;
1796 0 : rv = option->GetSelected(&isSelected);
1797 0 : NS_ENSURE_SUCCESS(rv, rv);
1798 0 : if (!isSelected) {
1799 0 : continue;
1800 : }
1801 :
1802 0 : nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = do_QueryInterface(option);
1803 0 : NS_ENSURE_TRUE(optionElement, NS_ERROR_UNEXPECTED);
1804 :
1805 0 : nsAutoString value;
1806 0 : rv = optionElement->GetValue(value);
1807 0 : NS_ENSURE_SUCCESS(rv, rv);
1808 :
1809 0 : if (keyGenProcessor) {
1810 0 : nsAutoString tmp(value);
1811 0 : rv = keyGenProcessor->ProcessValue(this, name, tmp);
1812 0 : if (NS_SUCCEEDED(rv)) {
1813 0 : value = tmp;
1814 : }
1815 : }
1816 :
1817 0 : rv = aFormSubmission->AddNameValuePair(name, value);
1818 : }
1819 :
1820 0 : return NS_OK;
1821 : }
1822 :
1823 : NS_IMETHODIMP
1824 0 : nsHTMLSelectElement::GetHasOptGroups(bool* aHasGroups)
1825 : {
1826 0 : *aHasGroups = (mOptGroupCount > 0);
1827 0 : return NS_OK;
1828 : }
1829 :
1830 : void
1831 0 : nsHTMLSelectElement::DispatchContentReset()
1832 : {
1833 0 : nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
1834 0 : if (formControlFrame) {
1835 : // Only dispatch content reset notification if this is a list control
1836 : // frame or combo box control frame.
1837 0 : if (IsCombobox()) {
1838 0 : nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame);
1839 0 : if (comboFrame) {
1840 0 : comboFrame->OnContentReset();
1841 : }
1842 : } else {
1843 0 : nsIListControlFrame* listFrame = do_QueryFrame(formControlFrame);
1844 0 : if (listFrame) {
1845 0 : listFrame->OnContentReset();
1846 : }
1847 : }
1848 : }
1849 0 : }
1850 :
1851 : static void
1852 0 : AddOptionsRecurse(nsIContent* aRoot, nsHTMLOptionCollection* aArray)
1853 : {
1854 0 : for (nsIContent* cur = aRoot->GetFirstChild();
1855 : cur;
1856 0 : cur = cur->GetNextSibling()) {
1857 0 : nsHTMLOptionElement* opt = nsHTMLOptionElement::FromContent(cur);
1858 0 : if (opt) {
1859 : // If we fail here, then at least we've tried our best
1860 0 : aArray->AppendOption(opt);
1861 0 : } else if (cur->IsHTML(nsGkAtoms::optgroup)) {
1862 0 : AddOptionsRecurse(cur, aArray);
1863 : }
1864 : }
1865 0 : }
1866 :
1867 : void
1868 0 : nsHTMLSelectElement::RebuildOptionsArray(bool aNotify)
1869 : {
1870 0 : mOptions->Clear();
1871 0 : AddOptionsRecurse(this, mOptions);
1872 0 : FindSelectedIndex(0, aNotify);
1873 0 : }
1874 :
1875 : bool
1876 0 : nsHTMLSelectElement::IsValueMissing()
1877 : {
1878 0 : if (!HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
1879 0 : return false;
1880 : }
1881 :
1882 : PRUint32 length;
1883 0 : mOptions->GetLength(&length);
1884 :
1885 0 : for (PRUint32 i = 0; i < length; ++i) {
1886 0 : nsIDOMHTMLOptionElement* option = mOptions->ItemAsOption(i);
1887 : bool selected;
1888 0 : NS_ENSURE_SUCCESS(option->GetSelected(&selected), false);
1889 :
1890 0 : if (!selected) {
1891 0 : continue;
1892 : }
1893 :
1894 : bool disabled;
1895 0 : IsOptionDisabled(i, &disabled);
1896 0 : if (disabled) {
1897 0 : continue;
1898 : }
1899 :
1900 0 : nsAutoString value;
1901 0 : NS_ENSURE_SUCCESS(option->GetValue(value), false);
1902 0 : if (!value.IsEmpty()) {
1903 0 : return false;
1904 : }
1905 : }
1906 :
1907 0 : return true;
1908 : }
1909 :
1910 : void
1911 0 : nsHTMLSelectElement::UpdateValueMissingValidityState()
1912 : {
1913 0 : SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
1914 0 : }
1915 :
1916 : nsresult
1917 0 : nsHTMLSelectElement::GetValidationMessage(nsAString& aValidationMessage,
1918 : ValidityStateType aType)
1919 : {
1920 0 : switch (aType) {
1921 : case VALIDITY_STATE_VALUE_MISSING: {
1922 0 : nsXPIDLString message;
1923 : nsresult rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
1924 : "FormValidationSelectMissing",
1925 0 : message);
1926 0 : aValidationMessage = message;
1927 0 : return rv;
1928 : }
1929 : default: {
1930 0 : return nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
1931 : }
1932 : }
1933 : }
1934 :
1935 : #ifdef DEBUG
1936 :
1937 : static void
1938 0 : VerifyOptionsRecurse(nsIContent* aRoot, PRInt32& aIndex,
1939 : nsHTMLOptionCollection* aArray)
1940 : {
1941 0 : for (nsIContent* cur = aRoot->GetFirstChild();
1942 : cur;
1943 0 : cur = cur->GetNextSibling()) {
1944 0 : nsCOMPtr<nsIDOMHTMLOptionElement> opt = do_QueryInterface(cur);
1945 0 : if (opt) {
1946 0 : NS_ASSERTION(opt == aArray->ItemAsOption(aIndex++),
1947 : "Options collection broken");
1948 0 : } else if (cur->IsHTML(nsGkAtoms::optgroup)) {
1949 0 : VerifyOptionsRecurse(cur, aIndex, aArray);
1950 : }
1951 : }
1952 0 : }
1953 :
1954 : void
1955 0 : nsHTMLSelectElement::VerifyOptionsArray()
1956 : {
1957 0 : PRInt32 aIndex = 0;
1958 0 : VerifyOptionsRecurse(this, aIndex, mOptions);
1959 0 : }
1960 :
1961 : #endif
1962 :
1963 : //----------------------------------------------------------------------
1964 : //
1965 : // nsHTMLOptionCollection implementation
1966 : //
1967 :
1968 0 : nsHTMLOptionCollection::nsHTMLOptionCollection(nsHTMLSelectElement* aSelect)
1969 : {
1970 0 : SetIsProxy();
1971 :
1972 : // Do not maintain a reference counted reference. When
1973 : // the select goes away, it will let us know.
1974 0 : mSelect = aSelect;
1975 0 : }
1976 :
1977 0 : nsHTMLOptionCollection::~nsHTMLOptionCollection()
1978 : {
1979 0 : DropReference();
1980 0 : }
1981 :
1982 : void
1983 0 : nsHTMLOptionCollection::DropReference()
1984 : {
1985 : // Drop our (non ref-counted) reference
1986 0 : mSelect = nsnull;
1987 0 : }
1988 :
1989 : nsresult
1990 0 : nsHTMLOptionCollection::GetOptionIndex(mozilla::dom::Element* aOption,
1991 : PRInt32 aStartIndex,
1992 : bool aForward,
1993 : PRInt32* aIndex)
1994 : {
1995 : // NOTE: aIndex shouldn't be set if the returned value isn't NS_OK.
1996 :
1997 : PRInt32 index;
1998 :
1999 : // Make the common case fast
2000 0 : if (aStartIndex == 0 && aForward) {
2001 0 : index = mElements.IndexOf(aOption);
2002 0 : if (index == -1) {
2003 0 : return NS_ERROR_FAILURE;
2004 : }
2005 :
2006 0 : *aIndex = index;
2007 0 : return NS_OK;
2008 : }
2009 :
2010 0 : PRInt32 high = mElements.Length();
2011 0 : PRInt32 step = aForward ? 1 : -1;
2012 :
2013 0 : for (index = aStartIndex; index < high && index > -1; index += step) {
2014 0 : if (mElements[index] == aOption) {
2015 0 : *aIndex = index;
2016 0 : return NS_OK;
2017 : }
2018 : }
2019 :
2020 0 : return NS_ERROR_FAILURE;
2021 : }
2022 :
2023 :
2024 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLOptionCollection)
2025 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHTMLOptionCollection)
2026 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mElements)
2027 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
2028 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
2029 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHTMLOptionCollection)
2030 : {
2031 : PRUint32 i;
2032 0 : for (i = 0; i < tmp->mElements.Length(); ++i) {
2033 0 : NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mElements[i]");
2034 0 : cb.NoteXPCOMChild(static_cast<Element*>(tmp->mElements[i]));
2035 : }
2036 : }
2037 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
2038 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
2039 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsHTMLOptionCollection)
2040 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
2041 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
2042 :
2043 : // nsISupports
2044 :
2045 : DOMCI_DATA(HTMLOptionsCollection, nsHTMLOptionCollection)
2046 :
2047 : // QueryInterface implementation for nsHTMLOptionCollection
2048 0 : NS_INTERFACE_TABLE_HEAD(nsHTMLOptionCollection)
2049 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
2050 0 : NS_INTERFACE_TABLE3(nsHTMLOptionCollection,
2051 : nsIHTMLCollection,
2052 : nsIDOMHTMLOptionsCollection,
2053 : nsIDOMHTMLCollection)
2054 0 : NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHTMLOptionCollection)
2055 0 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(HTMLOptionsCollection)
2056 0 : NS_INTERFACE_MAP_END
2057 :
2058 :
2059 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHTMLOptionCollection)
2060 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHTMLOptionCollection)
2061 :
2062 :
2063 : JSObject*
2064 0 : nsHTMLOptionCollection::WrapObject(JSContext *cx, XPCWrappedNativeScope *scope,
2065 : bool *triedToWrap)
2066 : {
2067 : return mozilla::dom::binding::HTMLOptionsCollection::create(cx, scope, this,
2068 0 : triedToWrap);
2069 : }
2070 :
2071 : NS_IMETHODIMP
2072 0 : nsHTMLOptionCollection::GetLength(PRUint32* aLength)
2073 : {
2074 0 : *aLength = mElements.Length();
2075 :
2076 0 : return NS_OK;
2077 : }
2078 :
2079 : NS_IMETHODIMP
2080 0 : nsHTMLOptionCollection::SetLength(PRUint32 aLength)
2081 : {
2082 0 : if (!mSelect) {
2083 0 : return NS_ERROR_UNEXPECTED;
2084 : }
2085 :
2086 0 : return mSelect->SetLength(aLength);
2087 : }
2088 :
2089 : NS_IMETHODIMP
2090 0 : nsHTMLOptionCollection::SetOption(PRUint32 aIndex,
2091 : nsIDOMHTMLOptionElement *aOption)
2092 : {
2093 0 : if (!mSelect) {
2094 0 : return NS_OK;
2095 : }
2096 :
2097 : // if the new option is null, just remove this option. Note that it's safe
2098 : // to pass a too-large aIndex in here.
2099 0 : if (!aOption) {
2100 0 : mSelect->Remove(aIndex);
2101 :
2102 : // We're done.
2103 0 : return NS_OK;
2104 : }
2105 :
2106 0 : nsresult rv = NS_OK;
2107 :
2108 0 : PRUint32 index = PRUint32(aIndex);
2109 :
2110 : // Now we're going to be setting an option in our collection
2111 0 : if (index > mElements.Length()) {
2112 : // Fill our array with blank options up to (but not including, since we're
2113 : // about to change it) aIndex, for compat with other browsers.
2114 0 : rv = SetLength(index);
2115 0 : NS_ENSURE_SUCCESS(rv, rv);
2116 : }
2117 :
2118 0 : NS_ASSERTION(index <= mElements.Length(), "SetLength lied");
2119 :
2120 0 : nsCOMPtr<nsIDOMNode> ret;
2121 0 : if (index == mElements.Length()) {
2122 0 : rv = mSelect->AppendChild(aOption, getter_AddRefs(ret));
2123 : } else {
2124 : // Find the option they're talking about and replace it
2125 : // hold a strong reference to follow COM rules.
2126 0 : nsCOMPtr<nsIDOMHTMLOptionElement> refChild = ItemAsOption(index);
2127 0 : NS_ENSURE_TRUE(refChild, NS_ERROR_UNEXPECTED);
2128 :
2129 0 : nsCOMPtr<nsIDOMNode> parent;
2130 0 : refChild->GetParentNode(getter_AddRefs(parent));
2131 0 : if (parent) {
2132 0 : rv = parent->ReplaceChild(aOption, refChild, getter_AddRefs(ret));
2133 : }
2134 : }
2135 :
2136 0 : return rv;
2137 : }
2138 :
2139 : NS_IMETHODIMP
2140 0 : nsHTMLOptionCollection::GetSelectedIndex(PRInt32 *aSelectedIndex)
2141 : {
2142 0 : NS_ENSURE_TRUE(mSelect, NS_ERROR_UNEXPECTED);
2143 :
2144 0 : return mSelect->GetSelectedIndex(aSelectedIndex);
2145 : }
2146 :
2147 : NS_IMETHODIMP
2148 0 : nsHTMLOptionCollection::SetSelectedIndex(PRInt32 aSelectedIndex)
2149 : {
2150 0 : NS_ENSURE_TRUE(mSelect, NS_ERROR_UNEXPECTED);
2151 :
2152 0 : return mSelect->SetSelectedIndex(aSelectedIndex);
2153 : }
2154 :
2155 : NS_IMETHODIMP
2156 0 : nsHTMLOptionCollection::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
2157 : {
2158 0 : nsISupports* item = GetNodeAt(aIndex);
2159 0 : if (!item) {
2160 0 : *aReturn = nsnull;
2161 :
2162 0 : return NS_OK;
2163 : }
2164 :
2165 0 : return CallQueryInterface(item, aReturn);
2166 : }
2167 :
2168 : nsIContent*
2169 0 : nsHTMLOptionCollection::GetNodeAt(PRUint32 aIndex)
2170 : {
2171 0 : return static_cast<nsIContent*>(ItemAsOption(aIndex));
2172 : }
2173 :
2174 : static nsHTMLOptionElement*
2175 0 : GetNamedItemHelper(nsTArray<nsRefPtr<nsHTMLOptionElement> > &aElements,
2176 : const nsAString& aName)
2177 : {
2178 0 : PRUint32 count = aElements.Length();
2179 0 : for (PRUint32 i = 0; i < count; i++) {
2180 0 : nsHTMLOptionElement *content = aElements.ElementAt(i);
2181 0 : if (content &&
2182 : (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, aName,
2183 0 : eCaseMatters) ||
2184 : content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::id, aName,
2185 0 : eCaseMatters))) {
2186 0 : return content;
2187 : }
2188 : }
2189 :
2190 0 : return nsnull;
2191 : }
2192 :
2193 : nsISupports*
2194 0 : nsHTMLOptionCollection::GetNamedItem(const nsAString& aName,
2195 : nsWrapperCache **aCache)
2196 : {
2197 0 : nsINode *item = GetNamedItemHelper(mElements, aName);
2198 0 : *aCache = item;
2199 0 : return item;
2200 : }
2201 :
2202 : nsINode*
2203 0 : nsHTMLOptionCollection::GetParentObject()
2204 : {
2205 0 : return mSelect;
2206 : }
2207 :
2208 : NS_IMETHODIMP
2209 0 : nsHTMLOptionCollection::NamedItem(const nsAString& aName,
2210 : nsIDOMNode** aReturn)
2211 : {
2212 0 : NS_IF_ADDREF(*aReturn = GetNamedItemHelper(mElements, aName));
2213 :
2214 0 : return NS_OK;
2215 : }
2216 :
2217 : NS_IMETHODIMP
2218 0 : nsHTMLOptionCollection::GetSelect(nsIDOMHTMLSelectElement **aReturn)
2219 : {
2220 0 : NS_IF_ADDREF(*aReturn = mSelect);
2221 0 : return NS_OK;
2222 : }
2223 :
2224 : NS_IMETHODIMP
2225 0 : nsHTMLOptionCollection::Add(nsIDOMHTMLOptionElement *aOption,
2226 : nsIVariant *aBefore)
2227 : {
2228 0 : if (!aOption) {
2229 0 : return NS_ERROR_INVALID_ARG;
2230 : }
2231 :
2232 0 : if (!mSelect) {
2233 0 : return NS_ERROR_NOT_INITIALIZED;
2234 : }
2235 :
2236 0 : return mSelect->Add(aOption, aBefore);
2237 : }
2238 :
2239 : NS_IMETHODIMP
2240 0 : nsHTMLOptionCollection::Remove(PRInt32 aIndex)
2241 : {
2242 0 : NS_ENSURE_TRUE(mSelect, NS_ERROR_UNEXPECTED);
2243 :
2244 0 : PRUint32 len = 0;
2245 0 : mSelect->GetLength(&len);
2246 0 : if (aIndex < 0 || (PRUint32)aIndex >= len)
2247 0 : aIndex = 0;
2248 :
2249 0 : return mSelect->Remove(aIndex);
2250 : }
2251 :
2252 : void
2253 0 : nsHTMLSelectElement::UpdateBarredFromConstraintValidation()
2254 : {
2255 0 : SetBarredFromConstraintValidation(IsDisabled());
2256 0 : }
2257 :
2258 : void
2259 0 : nsHTMLSelectElement::FieldSetDisabledChanged(bool aNotify)
2260 : {
2261 0 : UpdateBarredFromConstraintValidation();
2262 :
2263 0 : nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify);
2264 0 : }
2265 :
2266 : void
2267 0 : nsHTMLSelectElement::SetSelectionChanged(bool aValue, bool aNotify)
2268 : {
2269 0 : if (!mDefaultSelectionSet) {
2270 0 : return;
2271 : }
2272 :
2273 0 : bool previousSelectionChangedValue = mSelectionHasChanged;
2274 0 : mSelectionHasChanged = aValue;
2275 :
2276 0 : if (mSelectionHasChanged != previousSelectionChangedValue) {
2277 0 : UpdateState(aNotify);
2278 : }
2279 4392 : }
|