Back to index

lightning-sunbird  0.9+nobinonly
nsHTMLSelectElement.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Pierre Phaneuf <pp@ludusdesign.com>
00024  *   Mats Palmgren <mats.palmgren@bredband.net>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 #include "nsCOMPtr.h"
00040 #include "nsCOMArray.h"
00041 #include "nsIDOMHTMLSelectElement.h"
00042 #include "nsIDOMNSHTMLSelectElement.h"
00043 #include "nsIDOMNSXBLFormControl.h"
00044 #include "nsIDOMHTMLFormElement.h"
00045 #include "nsIDOMEventReceiver.h"
00046 #include "nsITextContent.h"
00047 #include "nsGenericHTMLElement.h"
00048 #include "nsHTMLAtoms.h"
00049 #include "nsStyleConsts.h"
00050 #include "nsPresContext.h"
00051 #include "nsMappedAttributes.h"
00052 #include "nsIForm.h"
00053 #include "nsIFormSubmission.h"
00054 #include "nsIDOMHTMLCollection.h"
00055 #include "nsIDOMHTMLOptionElement.h"
00056 #include "nsIDOMHTMLOptGroupElement.h"
00057 #include "nsIOptionElement.h"
00058 #include "nsIEventStateManager.h"
00059 #include "nsGenericDOMHTMLCollection.h"
00060 #include "nsISelectElement.h"
00061 #include "nsISelectControlFrame.h"
00062 #include "nsIDOMHTMLOptionsCollection.h"
00063 #include "nsIDOMNSHTMLOptionCollectn.h"
00064 #include "nsGUIEvent.h"
00065 #include "nsIPrivateDOMEvent.h"
00066 #include "nsIBoxObject.h"
00067 #include "nsIDOMNSDocument.h"
00068 #include "nsIDOMDocumentEvent.h"
00069 
00070 // PresState
00071 #include "nsXPCOM.h"
00072 #include "nsPresState.h"
00073 #include "nsIComponentManager.h"
00074 #include "nsCheapSets.h"
00075 
00076 // Notify/query select frame for selectedIndex
00077 #include "nsIDocument.h"
00078 #include "nsIPresShell.h"
00079 #include "nsIFormControlFrame.h"
00080 #include "nsIFrame.h"
00081 
00082 #include "nsDOMError.h"
00083 #include "nsRuleData.h"
00084 
00085 
00086 class nsHTMLSelectElement;
00087 
00092 class nsHTMLOptionCollection: public nsIDOMHTMLOptionsCollection,
00093                               public nsIDOMNSHTMLOptionCollection,
00094                               public nsGenericDOMHTMLCollection
00095 {
00096 public:
00097   nsHTMLOptionCollection(nsHTMLSelectElement* aSelect);
00098   virtual ~nsHTMLOptionCollection();
00099 
00100   NS_DECL_ISUPPORTS_INHERITED
00101 
00102   // nsIDOMHTMLOptionsCollection interface
00103   NS_DECL_NSIDOMHTMLOPTIONSCOLLECTION
00104 
00105   // nsIDOMNSHTMLOptionCollection interface
00106   NS_DECL_NSIDOMNSHTMLOPTIONCOLLECTION
00107 
00108   // nsIDOMHTMLCollection interface, all its methods are defined in
00109   // nsIDOMHTMLOptionsCollection
00110 
00111   // Helpers for nsHTMLSelectElement
00117   PRBool InsertOptionAt(nsIDOMHTMLOptionElement* aOption, PRInt32 aIndex)
00118   {
00119     return mElements.InsertObjectAt(aOption, aIndex);
00120   }
00121 
00126   void RemoveOptionAt(PRInt32 aIndex)
00127   {
00128     mElements.RemoveObjectAt(aIndex);
00129   }
00130 
00136   nsIDOMHTMLOptionElement *ItemAsOption(PRInt32 aIndex)
00137   {
00138     return mElements.SafeObjectAt(aIndex);
00139   }
00140 
00144   void Clear()
00145   {
00146     mElements.Clear();
00147   }
00148 
00152   PRBool AppendOption(nsIDOMHTMLOptionElement* aOption)
00153   {
00154     return mElements.AppendObject(aOption);
00155   }
00156 
00160   void DropReference();
00161 
00165   nsresult GetOptionIndex(nsIDOMHTMLOptionElement* aOption,
00166                           PRInt32 aStartIndex, PRBool aForward,
00167                           PRInt32* aIndex);
00168 
00169 private:
00171   nsCOMArray<nsIDOMHTMLOptionElement> mElements;
00173   nsHTMLSelectElement* mSelect;
00174 };
00175 
00176 
00180 class nsSelectState : public nsISupports {
00181 public:
00182   nsSelectState()
00183   {
00184   }
00185   virtual ~nsSelectState()
00186   {
00187   }
00188 
00189   NS_DECL_ISUPPORTS
00190 
00191   void PutOption(PRInt32 aIndex, const nsAString& aValue)
00192   {
00193     // If the option is empty, store the index.  If not, store the value.
00194     if (aValue.IsEmpty()) {
00195       mIndices.Put(aIndex);
00196     } else {
00197       mValues.Put(aValue);
00198     }
00199   }
00200 
00201   PRBool ContainsOption(PRInt32 aIndex, const nsAString& aValue)
00202   {
00203     return mValues.Contains(aValue) || mIndices.Contains(aIndex);
00204   }
00205 
00206 private:
00207   nsCheapStringSet mValues;
00208   nsCheapInt32Set mIndices;
00209 };
00210 
00211 NS_IMPL_ISUPPORTS0(nsSelectState)
00212 
00213 
00214 
00217 class nsHTMLSelectElement : public nsGenericHTMLFormElement,
00218                             public nsIDOMHTMLSelectElement,
00219                             public nsIDOMNSHTMLSelectElement,
00220                             public nsIDOMNSXBLFormControl,
00221                             public nsISelectElement
00222 {
00223 public:
00224   nsHTMLSelectElement(nsINodeInfo *aNodeInfo, PRBool aFromParser = PR_FALSE);
00225   virtual ~nsHTMLSelectElement();
00226 
00227   // nsISupports
00228   NS_DECL_ISUPPORTS_INHERITED
00229 
00230   // nsIDOMNode
00231   NS_FORWARD_NSIDOMNODE_NO_CLONENODE(nsGenericHTMLFormElement::)
00232 
00233   // nsIDOMElement
00234   NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLFormElement::)
00235 
00236   // nsIDOMHTMLElement
00237   NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLFormElement::)
00238 
00239   // nsIDOMHTMLSelectElement
00240   NS_DECL_NSIDOMHTMLSELECTELEMENT
00241 
00242   // nsIDOMNSHTMLSelectElement
00243   NS_DECL_NSIDOMNSHTMLSELECTELEMENT
00244 
00245   // nsIDOMNSXBLFormControl
00246   NS_DECL_NSIDOMNSXBLFORMCONTROL
00247 
00248   // nsIContent
00249   virtual nsresult InsertChildAt(nsIContent* aKid, PRUint32 aIndex,
00250                                  PRBool aNotify);
00251   virtual nsresult AppendChildTo(nsIContent* aKid, PRBool aNotify);
00252   virtual nsresult RemoveChildAt(PRUint32 aIndex, PRBool aNotify);
00253 
00254   virtual nsresult HandleDOMEvent(nsPresContext* aPresContext,
00255                                   nsEvent* aEvent, nsIDOMEvent** aDOMEvent,
00256                                   PRUint32 aFlags,
00257                                   nsEventStatus* aEventStatus);
00258 
00259   virtual void SetFocus(nsPresContext* aPresContext);
00260   virtual PRBool IsFocusable(PRInt32 *aTabIndex = nsnull);
00261 
00262   // Overriden nsIFormControl methods
00263   NS_IMETHOD_(PRInt32) GetType() const { return NS_FORM_SELECT; }
00264   NS_IMETHOD Reset();
00265   NS_IMETHOD SubmitNamesValues(nsIFormSubmission* aFormSubmission,
00266                                nsIContent* aSubmitElement);
00267   NS_IMETHOD SaveState();
00268   virtual PRBool RestoreState(nsPresState* aState);
00269 
00270   // nsISelectElement
00271   NS_DECL_NSISELECTELEMENT
00272 
00273   virtual void DoneAddingChildren();
00274   virtual PRBool IsDoneAddingChildren();
00275 
00276   virtual PRBool ParseAttribute(nsIAtom* aAttribute,
00277                                 const nsAString& aValue,
00278                                 nsAttrValue& aResult);
00279   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
00280   virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
00281                                               PRInt32 aModType) const;
00282   NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* aAttribute) const;
00283 
00284 
00285 protected:
00286   // Helper Methods
00292   PRBool IsOptionSelectedByIndex(PRInt32 aIndex);
00298   void FindSelectedIndex(PRInt32 aStartIndex);
00303   PRBool SelectSomething();
00309   PRBool CheckSelectSomething();
00319   void OnOptionSelected(nsISelectControlFrame* aSelectFrame,
00320                         nsPresContext* aPresContext,
00321                         PRInt32 aIndex,
00322                         PRBool aSelected,
00323                         PRBool aNotify);
00328   void RestoreStateTo(nsSelectState* aNewSelected);
00329 
00330 #ifdef DEBUG_john
00331   // Don't remove these, por favor.  They're very useful in debugging
00332   nsresult PrintOptions(nsIContent* aOptions, PRInt32 tabs);
00333 #endif
00334 
00335   // Adding options
00342   nsresult InsertOptionsIntoList(nsIContent* aOptions,
00343                                  PRInt32 aListIndex,
00344                                  PRInt32 aDepth);
00351   nsresult RemoveOptionsFromList(nsIContent* aOptions,
00352                                  PRInt32 aListIndex,
00353                                  PRInt32 aDepth);
00360   nsresult InsertOptionsIntoListRecurse(nsIContent* aOptions,
00361                                         PRInt32* aInsertIndex,
00362                                         PRInt32 aDepth);
00370   nsresult RemoveOptionsFromListRecurse(nsIContent* aOptions,
00371                                         PRInt32 aRemoveIndex,
00372                                         PRInt32* aNumRemoved,
00373                                         PRInt32 aDepth);
00379   PRInt32 GetContentDepth(nsIContent* aContent);
00386   PRInt32 GetOptionIndexAt(nsIContent* aOptions);
00394   PRInt32 GetOptionIndexAfter(nsIContent* aOptions);
00400   PRInt32 GetFirstOptionIndex(nsIContent* aOptions);
00409   PRInt32 GetFirstChildOptionIndex(nsIContent* aOptions,
00410                                    PRInt32 aStartIndex,
00411                                    PRInt32 aEndIndex);
00412 
00417   nsISelectControlFrame *GetSelectFrame();
00418 
00424   void DispatchDOMEvent(const nsAString& aName);
00425 
00429   PRBool IsCombobox() {
00430     PRBool isMultiple = PR_TRUE;
00431     PRInt32 size = 1;
00432     GetSize(&size);
00433     GetMultiple(&isMultiple);
00434     return !isMultiple && size <= 1;
00435   }
00436 
00440   void RebuildOptionsArray();
00441 
00443   nsRefPtr<nsHTMLOptionCollection> mOptions;
00445   PRBool    mIsDoneAddingChildren;
00447   PRUint32  mNonOptionChildren;
00449   PRUint32  mOptGroupCount;
00454   PRInt32   mSelectedIndex;
00459   nsRefPtr<nsSelectState> mRestoreState;
00460 };
00461 
00462 
00463 //----------------------------------------------------------------------
00464 //
00465 // nsHTMLSelectElement
00466 //
00467 
00468 // construction, destruction
00469 
00470 
00471 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Select)
00472 
00473 nsHTMLSelectElement::nsHTMLSelectElement(nsINodeInfo *aNodeInfo,
00474                                          PRBool aFromParser)
00475   : nsGenericHTMLFormElement(aNodeInfo),
00476     mOptions(new nsHTMLOptionCollection(this)),
00477     mIsDoneAddingChildren(!aFromParser),
00478     mNonOptionChildren(0),
00479     mOptGroupCount(0),
00480     mSelectedIndex(-1)
00481 {
00482   // FIXME: Bug 328908, set mOptions in an Init function and get rid of null
00483   // checks.
00484 
00485   // DoneAddingChildren() will be called later if it's from the parser,
00486   // otherwise it is
00487 }
00488 
00489 nsHTMLSelectElement::~nsHTMLSelectElement()
00490 {
00491   if (mOptions) {
00492     mOptions->DropReference();
00493   }
00494 }
00495 
00496 // ISupports
00497 
00498 NS_IMPL_ADDREF_INHERITED(nsHTMLSelectElement, nsGenericElement)
00499 NS_IMPL_RELEASE_INHERITED(nsHTMLSelectElement, nsGenericElement)
00500 
00501 
00502 // QueryInterface implementation for nsHTMLSelectElement
00503 NS_HTML_CONTENT_INTERFACE_MAP_BEGIN(nsHTMLSelectElement,
00504                                     nsGenericHTMLFormElement)
00505   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLSelectElement)
00506   NS_INTERFACE_MAP_ENTRY(nsIDOMNSHTMLSelectElement)
00507   NS_INTERFACE_MAP_ENTRY(nsIDOMNSXBLFormControl)
00508   NS_INTERFACE_MAP_ENTRY(nsISelectElement)
00509   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(HTMLSelectElement)
00510 NS_HTML_CONTENT_INTERFACE_MAP_END
00511 
00512 
00513 // nsIDOMHTMLSelectElement
00514 
00515 
00516 NS_IMPL_DOM_CLONENODE(nsHTMLSelectElement)
00517 
00518 
00519 NS_IMETHODIMP
00520 nsHTMLSelectElement::GetForm(nsIDOMHTMLFormElement** aForm)
00521 {
00522   return nsGenericHTMLFormElement::GetForm(aForm);
00523 }
00524 
00525 
00526 // nsIContent
00527 nsresult
00528 nsHTMLSelectElement::AppendChildTo(nsIContent* aKid, PRBool aNotify)
00529 {
00530   PRUint32 prevOptGroups = mOptGroupCount;
00531 
00532   nsresult rv = WillAddOptions(aKid, this, GetChildCount());
00533   PRBool rebuild = NS_FAILED(rv);
00534 
00535   rv = nsGenericHTMLFormElement::AppendChildTo(aKid, aNotify);
00536   if (rebuild || NS_FAILED(rv)) {
00537     RebuildOptionsArray();
00538     return rv;
00539   }
00540 
00541   if (mOptGroupCount && !prevOptGroups) {
00542     DispatchDOMEvent(NS_LITERAL_STRING("selectHasGroups"));
00543   }
00544 
00545   return NS_OK;
00546 }
00547 
00548 nsresult
00549 nsHTMLSelectElement::InsertChildAt(nsIContent* aKid, PRUint32 aIndex,
00550                                    PRBool aNotify)
00551 {
00552   PRUint32 prevOptGroups = mOptGroupCount;
00553 
00554   nsresult rv = WillAddOptions(aKid, this, aIndex);
00555   PRBool rebuild = NS_FAILED(rv);
00556 
00557   rv = nsGenericHTMLFormElement::InsertChildAt(aKid, aIndex, aNotify);
00558   if (rebuild || NS_FAILED(rv)) {
00559     RebuildOptionsArray();
00560     return rv;
00561   }
00562 
00563   if (mOptGroupCount && !prevOptGroups) {
00564     DispatchDOMEvent(NS_LITERAL_STRING("selectHasGroups"));
00565   }
00566 
00567   return NS_OK;
00568 }
00569 
00570 nsresult
00571 nsHTMLSelectElement::RemoveChildAt(PRUint32 aIndex, PRBool aNotify)
00572 {
00573   PRUint32 prevOptGroups = mOptGroupCount;
00574 
00575   nsresult rv = WillRemoveOptions(this, aIndex);
00576   PRBool rebuild = NS_FAILED(rv);
00577 
00578   rv = nsGenericHTMLFormElement::RemoveChildAt(aIndex, aNotify);
00579   if (rebuild || NS_FAILED(rv)) {
00580     RebuildOptionsArray();
00581     return rv;
00582   }
00583 
00584   if (!mOptGroupCount && prevOptGroups) {
00585     DispatchDOMEvent(NS_LITERAL_STRING("selectHasNoGroups"));
00586   }
00587 
00588   return NS_OK;
00589 }
00590 
00591 
00592 // SelectElement methods
00593 
00594 nsresult
00595 nsHTMLSelectElement::InsertOptionsIntoList(nsIContent* aOptions,
00596                                            PRInt32 aListIndex,
00597                                            PRInt32 aDepth)
00598 {
00599   PRInt32 insertIndex = aListIndex;
00600   nsresult rv = InsertOptionsIntoListRecurse(aOptions, &insertIndex, aDepth);
00601   NS_ENSURE_SUCCESS(rv, rv);
00602 
00603   // Deal with the selected list
00604   if (insertIndex - aListIndex) {
00605     // Fix the currently selected index
00606     if (aListIndex <= mSelectedIndex) {
00607       mSelectedIndex += (insertIndex - aListIndex);
00608     }
00609 
00610     // Get the frame stuff for notification. No need to flush here
00611     // since if there's no frame for the select yet the select will
00612     // get into the right state once it's created.
00613     nsISelectControlFrame* selectFrame = GetSelectFrame();
00614 
00615     nsPresContext *presContext = nsnull;
00616     if (selectFrame) {
00617       presContext = GetPresContext();
00618     }
00619 
00620     // Actually select the options if the added options warrant it
00621     nsCOMPtr<nsIDOMNode> optionNode;
00622     nsCOMPtr<nsIDOMHTMLOptionElement> option;
00623     for (PRInt32 i=aListIndex;i<insertIndex;i++) {
00624       // Notify the frame that the option is added
00625       if (selectFrame) {
00626         selectFrame->AddOption(presContext, i);
00627       }
00628 
00629       Item(i, getter_AddRefs(optionNode));
00630       option = do_QueryInterface(optionNode);
00631       if (option) {
00632         PRBool selected;
00633         option->GetSelected(&selected);
00634         if (selected) {
00635           // Clear all other options
00636           PRBool isMultiple;
00637           GetMultiple(&isMultiple);
00638           if (!isMultiple) {
00639             SetOptionsSelectedByIndex(i, i, PR_TRUE, PR_TRUE, PR_TRUE, PR_TRUE, nsnull);
00640           }
00641 
00642           // This is sort of a hack ... we need to notify that the option was
00643           // set and change selectedIndex even though we didn't really change
00644           // its value.
00645           OnOptionSelected(selectFrame, presContext, i, PR_TRUE, PR_FALSE);
00646         }
00647       }
00648     }
00649 
00650     CheckSelectSomething();
00651   }
00652 
00653   return NS_OK;
00654 }
00655 
00656 #ifdef DEBUG_john
00657 nsresult
00658 nsHTMLSelectElement::PrintOptions(nsIContent* aOptions, PRInt32 tabs)
00659 {
00660   for (PRInt32 i=0;i<tabs;i++) {
00661     printf("  ");
00662   }
00663 
00664   nsCOMPtr<nsIDOMHTMLElement> elem(do_QueryInterface(aOptions));
00665   if (elem) {
00666     nsAutoString s;
00667     elem->GetTagName(s);
00668     printf("<%s>\n", NS_ConvertUCS2toUTF8(s).get());
00669   } else {
00670     printf(">>text\n");
00671   }
00672 
00673   // Recurse down into optgroups
00674   if (IsOptGroup(aOptions)) {
00675     PRUint32 numChildren = aOptions->GetChildCount();
00676 
00677     for (PRUint32 i = 0; i < numChildren; ++i) {
00678       PrintOptions(aOptions->GetChildAt(i), tabs + 1);
00679     }
00680   }
00681 
00682   return NS_OK;
00683 }
00684 #endif
00685 
00686 nsresult
00687 nsHTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions,
00688                                            PRInt32 aListIndex,
00689                                            PRInt32 aDepth)
00690 {
00691   PRInt32 numRemoved = 0;
00692   nsresult rv = RemoveOptionsFromListRecurse(aOptions, aListIndex, &numRemoved,
00693                                              aDepth);
00694   NS_ENSURE_SUCCESS(rv, rv);
00695 
00696   if (numRemoved) {
00697     // Tell the widget we removed the options
00698     nsISelectControlFrame* selectFrame = GetSelectFrame();
00699     if (selectFrame) {
00700       nsPresContext *presContext = GetPresContext();
00701       for (int i = aListIndex; i < aListIndex + numRemoved; ++i) {
00702         selectFrame->RemoveOption(presContext, i);
00703       }
00704     }
00705 
00706     // Fix the selected index
00707     if (aListIndex <= mSelectedIndex) {
00708       if (mSelectedIndex < (aListIndex+numRemoved)) {
00709         // aListIndex <= mSelectedIndex < aListIndex+numRemoved
00710         // Find a new selected index if it was one of the ones removed.
00711         FindSelectedIndex(aListIndex);
00712       } else {
00713         // Shift the selected index if something in front of it was removed
00714         // aListIndex+numRemoved <= mSelectedIndex
00715         mSelectedIndex -= numRemoved;
00716       }
00717     }
00718 
00719     // Select something in case we removed the selected option on a
00720     // single select
00721     CheckSelectSomething();
00722   }
00723 
00724   return NS_OK;
00725 }
00726 
00727 static PRBool IsOptGroup(nsIContent *aContent)
00728 {
00729   nsINodeInfo *ni = aContent->GetNodeInfo();
00730 
00731   return (ni && ni->Equals(nsHTMLAtoms::optgroup) &&
00732           aContent->IsContentOfType(nsIContent::eHTML));
00733 }
00734 
00735 // If the document is such that recursing over these options gets us
00736 // deeper than four levels, there is something terribly wrong with the
00737 // world.
00738 nsresult
00739 nsHTMLSelectElement::InsertOptionsIntoListRecurse(nsIContent* aOptions,
00740                                                   PRInt32* aInsertIndex,
00741                                                   PRInt32 aDepth)
00742 {
00743   // We *assume* here that someone's brain has not gone horribly
00744   // wrong by putting <option> inside of <option>.  I'm sorry, I'm
00745   // just not going to look for an option inside of an option.
00746   // Sue me.
00747 
00748   nsCOMPtr<nsIDOMHTMLOptionElement> optElement(do_QueryInterface(aOptions));
00749   if (optElement) {
00750     nsresult rv = mOptions->InsertOptionAt(optElement, *aInsertIndex);
00751     NS_ENSURE_SUCCESS(rv, rv);
00752     (*aInsertIndex)++;
00753     return NS_OK;
00754   }
00755 
00756   // If it's at the top level, then we just found out there are non-options
00757   // at the top level, which will throw off the insert count
00758   if (aDepth == 0) {
00759     mNonOptionChildren++;
00760   }
00761 
00762   // Recurse down into optgroups
00763   if (IsOptGroup(aOptions)) {
00764     mOptGroupCount++;
00765 
00766     PRUint32 numChildren = aOptions->GetChildCount();
00767     for (PRUint32 i = 0; i < numChildren; ++i) {
00768       nsresult rv = InsertOptionsIntoListRecurse(aOptions->GetChildAt(i),
00769                                                  aInsertIndex, aDepth+1);
00770       NS_ENSURE_SUCCESS(rv, rv);
00771     }
00772   }
00773 
00774   return NS_OK;
00775 }
00776 
00777 // If the document is such that recursing over these options gets us deeper than
00778 // four levels, there is something terribly wrong with the world.
00779 nsresult
00780 nsHTMLSelectElement::RemoveOptionsFromListRecurse(nsIContent* aOptions,
00781                                                   PRInt32 aRemoveIndex,
00782                                                   PRInt32* aNumRemoved,
00783                                                   PRInt32 aDepth)
00784 {
00785   // We *assume* here that someone's brain has not gone horribly
00786   // wrong by putting <option> inside of <option>.  I'm sorry, I'm
00787   // just not going to look for an option inside of an option.
00788   // Sue me.
00789 
00790   nsCOMPtr<nsIDOMHTMLOptionElement> optElement(do_QueryInterface(aOptions));
00791   if (optElement) {
00792     if (mOptions->ItemAsOption(aRemoveIndex) != optElement) {
00793       NS_ERROR("wrong option at index");
00794       return NS_ERROR_UNEXPECTED;
00795     }
00796     mOptions->RemoveOptionAt(aRemoveIndex);
00797     (*aNumRemoved)++;
00798     return NS_OK;
00799   }
00800 
00801   // Yay, one less artifact at the top level.
00802   if (aDepth == 0) {
00803     mNonOptionChildren--;
00804   }
00805 
00806   // Recurse down deeper for options
00807   if (mOptGroupCount && IsOptGroup(aOptions)) {
00808     mOptGroupCount--;
00809 
00810     PRUint32 numChildren = aOptions->GetChildCount();
00811     for (PRUint32 i = 0; i < numChildren; ++i) {
00812       nsresult rv = RemoveOptionsFromListRecurse(aOptions->GetChildAt(i),
00813                                                  aRemoveIndex,
00814                                                  aNumRemoved,
00815                                                  aDepth + 1);
00816       NS_ENSURE_SUCCESS(rv, rv);
00817     }
00818   }
00819 
00820   return NS_OK;
00821 }
00822 
00823 // XXXldb Doing the processing before the content nodes have been added
00824 // to the document (as the name of this function seems to require, and
00825 // as the callers do), is highly unusual.  Passing around unparented
00826 // content to other parts of the app can make those things think the
00827 // options are the root content node.
00828 NS_IMETHODIMP
00829 nsHTMLSelectElement::WillAddOptions(nsIContent* aOptions,
00830                                     nsIContent* aParent,
00831                                     PRInt32 aContentIndex)
00832 {
00833   PRInt32 level = GetContentDepth(aParent);
00834   if (level == -1) {
00835     return NS_ERROR_FAILURE;
00836   }
00837 
00838   // Get the index where the options will be inserted
00839   PRInt32 ind = -1;
00840   if (!mNonOptionChildren) {
00841     // If there are no artifacts, aContentIndex == ind
00842     ind = aContentIndex;
00843   } else {
00844     // If there are artifacts, we have to get the index of the option the
00845     // hard way
00846     PRInt32 children = aParent->GetChildCount();
00847 
00848     if (aContentIndex >= children) {
00849       // If the content insert is after the end of the parent, then we want to get
00850       // the next index *after* the parent and insert there.
00851       ind = GetOptionIndexAfter(aParent);
00852     } else {
00853       // If the content insert is somewhere in the middle of the container, then
00854       // we want to get the option currently at the index and insert in front of
00855       // that.
00856       nsIContent *currentKid = aParent->GetChildAt(aContentIndex);
00857       NS_ASSERTION(currentKid, "Child not found!");
00858       if (currentKid) {
00859         ind = GetOptionIndexAt(currentKid);
00860       } else {
00861         ind = -1;
00862       }
00863     }
00864   }
00865 
00866   return InsertOptionsIntoList(aOptions, ind, level);
00867 }
00868 
00869 NS_IMETHODIMP
00870 nsHTMLSelectElement::WillRemoveOptions(nsIContent* aParent,
00871                                        PRInt32 aContentIndex)
00872 {
00873   PRInt32 level = GetContentDepth(aParent);
00874   NS_ASSERTION(level >= 0, "getting notified by unexpected content");
00875   if (level == -1) {
00876     return NS_ERROR_FAILURE;
00877   }
00878 
00879   // Get the index where the options will be removed
00880   nsIContent *currentKid = aParent->GetChildAt(aContentIndex);
00881   if (currentKid) {
00882     PRInt32 ind;
00883     if (!mNonOptionChildren) {
00884       // If there are no artifacts, aContentIndex == ind
00885       ind = aContentIndex;
00886     } else {
00887       // If there are artifacts, we have to get the index of the option the
00888       // hard way
00889       ind = GetFirstOptionIndex(currentKid);
00890     }
00891     if (ind != -1) {
00892       nsresult rv = RemoveOptionsFromList(currentKid, ind, level);
00893       NS_ENSURE_SUCCESS(rv, rv);
00894     }
00895   }
00896 
00897   return NS_OK;
00898 }
00899 
00900 PRInt32
00901 nsHTMLSelectElement::GetContentDepth(nsIContent* aContent)
00902 {
00903   nsIContent* content = aContent;
00904 
00905   PRInt32 retval = 0;
00906   while (content != this) {
00907     retval++;
00908     content = content->GetParent();
00909     if (!content) {
00910       retval = -1;
00911       break;
00912     }
00913   }
00914 
00915   return retval;
00916 }
00917 
00918 PRInt32
00919 nsHTMLSelectElement::GetOptionIndexAt(nsIContent* aOptions)
00920 {
00921   // Search this node and below.
00922   // If not found, find the first one *after* this node.
00923   PRInt32 retval = GetFirstOptionIndex(aOptions);
00924   if (retval == -1) {
00925     retval = GetOptionIndexAfter(aOptions);
00926   }
00927 
00928   return retval;
00929 }
00930 
00931 PRInt32
00932 nsHTMLSelectElement::GetOptionIndexAfter(nsIContent* aOptions)
00933 {
00934   // - If this is the select, the next option is the last.
00935   // - If not, search all the options after aOptions and up to the last option
00936   //   in the parent.
00937   // - If it's not there, search for the first option after the parent.
00938   if (aOptions == this) {
00939     PRUint32 len;
00940     GetLength(&len);
00941     return len;
00942   }
00943 
00944   PRInt32 retval = -1;
00945 
00946   nsCOMPtr<nsIContent> parent = aOptions->GetParent();
00947 
00948   if (parent) {
00949     PRInt32 index = parent->IndexOf(aOptions);
00950     PRInt32 count = parent->GetChildCount();
00951 
00952     retval = GetFirstChildOptionIndex(parent, index+1, count);
00953 
00954     if (retval == -1) {
00955       retval = GetOptionIndexAfter(parent);
00956     }
00957   }
00958 
00959   return retval;
00960 }
00961 
00962 PRInt32
00963 nsHTMLSelectElement::GetFirstOptionIndex(nsIContent* aOptions)
00964 {
00965   PRInt32 listIndex = -1;
00966   nsCOMPtr<nsIDOMHTMLOptionElement> optElement(do_QueryInterface(aOptions));
00967   if (optElement) {
00968     GetOptionIndex(optElement, 0, PR_TRUE, &listIndex);
00969     // If you nested stuff under the option, you're just plain
00970     // screwed.  *I'm* not going to aid and abet your evil deed.
00971     return listIndex;
00972   }
00973 
00974   listIndex = GetFirstChildOptionIndex(aOptions, 0, aOptions->GetChildCount());
00975 
00976   return listIndex;
00977 }
00978 
00979 PRInt32
00980 nsHTMLSelectElement::GetFirstChildOptionIndex(nsIContent* aOptions,
00981                                               PRInt32 aStartIndex,
00982                                               PRInt32 aEndIndex)
00983 {
00984   PRInt32 retval = -1;
00985 
00986   for (PRInt32 i = aStartIndex; i < aEndIndex; ++i) {
00987     retval = GetFirstOptionIndex(aOptions->GetChildAt(i));
00988     if (retval != -1) {
00989       break;
00990     }
00991   }
00992 
00993   return retval;
00994 }
00995 
00996 nsISelectControlFrame *
00997 nsHTMLSelectElement::GetSelectFrame()
00998 {
00999   nsIFormControlFrame* form_control_frame = GetFormControlFrame(PR_FALSE);
01000 
01001   nsISelectControlFrame *select_frame = nsnull;
01002 
01003   if (form_control_frame) {
01004     CallQueryInterface(form_control_frame, &select_frame);
01005   }
01006 
01007   return select_frame;
01008 }
01009 
01010 NS_IMETHODIMP
01011 nsHTMLSelectElement::Add(nsIDOMHTMLElement* aElement,
01012                          nsIDOMHTMLElement* aBefore)
01013 {
01014   nsCOMPtr<nsIDOMNode> added;
01015   if (!aBefore) {
01016     return AppendChild(aElement, getter_AddRefs(added));
01017   }
01018 
01019   // Just in case we're not the parent, get the parent of the reference
01020   // element
01021   nsCOMPtr<nsIDOMNode> parent;
01022   aBefore->GetParentNode(getter_AddRefs(parent));
01023   if (!parent) {
01024     // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT
01025     // element.
01026     return NS_ERROR_DOM_NOT_FOUND_ERR;
01027   }
01028 
01029   nsCOMPtr<nsIDOMNode> ancestor(parent);
01030   nsCOMPtr<nsIDOMNode> temp;
01031   while (ancestor != NS_STATIC_CAST(nsIDOMNode*, this)) {
01032     ancestor->GetParentNode(getter_AddRefs(temp));
01033     if (!temp) {
01034       // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT
01035       // element.
01036       return NS_ERROR_DOM_NOT_FOUND_ERR;
01037     }
01038     temp.swap(ancestor);
01039   }
01040 
01041   // If the before parameter is not null, we are equivalent to the
01042   // insertBefore method on the parent of before.
01043   return parent->InsertBefore(aElement, aBefore, getter_AddRefs(added));
01044 }
01045 
01046 NS_IMETHODIMP
01047 nsHTMLSelectElement::Remove(PRInt32 aIndex)
01048 {
01049   nsCOMPtr<nsIDOMNode> option;
01050   Item(aIndex, getter_AddRefs(option));
01051 
01052   if (option) {
01053     nsCOMPtr<nsIDOMNode> parent;
01054 
01055     option->GetParentNode(getter_AddRefs(parent));
01056     if (parent) {
01057       nsCOMPtr<nsIDOMNode> ret;
01058       parent->RemoveChild(option, getter_AddRefs(ret));
01059     }
01060   }
01061 
01062   return NS_OK;
01063 }
01064 
01065 NS_IMETHODIMP
01066 nsHTMLSelectElement::GetOptions(nsIDOMHTMLOptionsCollection** aValue)
01067 {
01068   *aValue = mOptions;
01069   NS_IF_ADDREF(*aValue);
01070 
01071   return NS_OK;
01072 }
01073 
01074 NS_IMETHODIMP
01075 nsHTMLSelectElement::GetType(nsAString& aType)
01076 {
01077   PRBool isMultiple;
01078   GetMultiple(&isMultiple);
01079   if (isMultiple) {
01080     aType.AssignLiteral("select-multiple");
01081   }
01082   else {
01083     aType.AssignLiteral("select-one");
01084   }
01085 
01086   return NS_OK;
01087 }
01088 
01089 NS_IMETHODIMP
01090 nsHTMLSelectElement::GetLength(PRUint32* aLength)
01091 {
01092   return mOptions->GetLength(aLength);
01093 }
01094 
01095 NS_IMETHODIMP
01096 nsHTMLSelectElement::SetLength(PRUint32 aLength)
01097 {
01098   nsresult rv=NS_OK;
01099 
01100   PRUint32 curlen;
01101   PRInt32 i;
01102 
01103   rv = GetLength(&curlen);
01104   if (NS_FAILED(rv)) {
01105     curlen = 0;
01106   }
01107 
01108   if (curlen && (curlen > aLength)) { // Remove extra options
01109     for (i = (curlen - 1); (i >= (PRInt32)aLength) && NS_SUCCEEDED(rv); i--) {
01110       rv = Remove(i);
01111     }
01112   } else if (aLength) {
01113     // This violates the W3C DOM but we do this for backwards compatibility
01114     nsCOMPtr<nsINodeInfo> nodeInfo;
01115 
01116     nsContentUtils::NameChanged(mNodeInfo, nsHTMLAtoms::option,
01117                                 getter_AddRefs(nodeInfo));
01118 
01119     nsCOMPtr<nsIContent> element = NS_NewHTMLOptionElement(nodeInfo);
01120     if (!element) {
01121       return NS_ERROR_OUT_OF_MEMORY;
01122     }
01123 
01124     nsCOMPtr<nsITextContent> text;
01125     rv = NS_NewTextNode(getter_AddRefs(text), mNodeInfo->NodeInfoManager());
01126     NS_ENSURE_SUCCESS(rv, rv);
01127 
01128     rv = element->AppendChildTo(text, PR_FALSE);
01129     NS_ENSURE_SUCCESS(rv, rv);
01130 
01131     nsCOMPtr<nsIDOMNode> node(do_QueryInterface(element));
01132 
01133     for (i = curlen; i < (PRInt32)aLength; i++) {
01134       nsCOMPtr<nsIDOMNode> tmpNode;
01135 
01136       rv = AppendChild(node, getter_AddRefs(tmpNode));
01137       NS_ENSURE_SUCCESS(rv, rv);
01138 
01139       if (i < ((PRInt32)aLength - 1)) {
01140         nsCOMPtr<nsIDOMNode> newNode;
01141 
01142         rv = node->CloneNode(PR_TRUE, getter_AddRefs(newNode));
01143         NS_ENSURE_SUCCESS(rv, rv);
01144 
01145         node = newNode;
01146       }
01147     }
01148   }
01149 
01150   return NS_OK;
01151 }
01152 
01153 //NS_IMPL_INT_ATTR(nsHTMLSelectElement, SelectedIndex, selectedindex)
01154 
01155 NS_IMETHODIMP
01156 nsHTMLSelectElement::GetSelectedIndex(PRInt32* aValue)
01157 {
01158   *aValue = mSelectedIndex;
01159 
01160   return NS_OK;
01161 }
01162 
01163 NS_IMETHODIMP
01164 nsHTMLSelectElement::SetSelectedIndex(PRInt32 aIndex)
01165 {
01166   PRInt32 oldSelectedIndex = mSelectedIndex;
01167 
01168   nsresult rv = SetOptionsSelectedByIndex(aIndex, aIndex, PR_TRUE,
01169                                           PR_TRUE, PR_TRUE, PR_TRUE, nsnull);
01170 
01171   if (NS_SUCCEEDED(rv)) {
01172     nsISelectControlFrame* selectFrame = GetSelectFrame();
01173     if (selectFrame) {
01174       rv = selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex);
01175     }
01176   }
01177 
01178   return rv;
01179 }
01180 
01181 NS_IMETHODIMP
01182 nsHTMLSelectElement::GetOptionIndex(nsIDOMHTMLOptionElement* aOption,
01183                                     PRInt32 aStartIndex, PRBool aForward,
01184                                     PRInt32* aIndex)
01185 {
01186   return mOptions->GetOptionIndex(aOption, aStartIndex, aForward, aIndex);
01187 }
01188 
01189 PRBool
01190 nsHTMLSelectElement::IsOptionSelectedByIndex(PRInt32 aIndex)
01191 {
01192   nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(aIndex);
01193   PRBool isSelected = PR_FALSE;
01194   if (option) {
01195     option->GetSelected(&isSelected);
01196   }
01197   return isSelected;
01198 }
01199 
01200 void
01201 nsHTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame,
01202                                       nsPresContext* aPresContext,
01203                                       PRInt32 aIndex,
01204                                       PRBool aSelected,
01205                                       PRBool aNotify)
01206 {
01207   // Set the selected index
01208   if (aSelected && (aIndex < mSelectedIndex || mSelectedIndex < 0)) {
01209     mSelectedIndex = aIndex;
01210   } else if (!aSelected && aIndex == mSelectedIndex) {
01211     FindSelectedIndex(aIndex+1);
01212   }
01213 
01214   // Tell the option to get its bad self selected
01215   nsCOMPtr<nsIDOMNode> option;
01216   Item(aIndex, getter_AddRefs(option));
01217   if (option) {
01218     nsCOMPtr<nsIOptionElement> optionElement(do_QueryInterface(option));
01219     optionElement->SetSelectedInternal(aSelected, aNotify);
01220   }
01221 
01222   // Let the frame know too
01223   if (aSelectFrame) {
01224     aSelectFrame->OnOptionSelected(aPresContext, aIndex, aSelected);
01225   }
01226 }
01227 
01228 void
01229 nsHTMLSelectElement::FindSelectedIndex(PRInt32 aStartIndex)
01230 {
01231   mSelectedIndex = -1;
01232   PRUint32 len;
01233   GetLength(&len);
01234   for (PRInt32 i=aStartIndex; i<(PRInt32)len; i++) {
01235     if (IsOptionSelectedByIndex(i)) {
01236       mSelectedIndex = i;
01237       break;
01238     }
01239   }
01240 }
01241 
01242 // XXX Consider splitting this into two functions for ease of reading:
01243 // SelectOptionsByIndex(startIndex, endIndex, clearAll, checkDisabled)
01244 //   startIndex, endIndex - the range of options to turn on
01245 //                          (-1, -1) will clear all indices no matter what.
01246 //   clearAll - will clear all other options unless checkDisabled is on
01247 //              and all the options attempted to be set are disabled
01248 //              (note that if it is not multiple, and an option is selected,
01249 //              everything else will be cleared regardless).
01250 //   checkDisabled - if this is TRUE, and an option is disabled, it will not be
01251 //                   changed regardless of whether it is selected or not.
01252 //                   Generally the UI passes TRUE and JS passes FALSE.
01253 //                   (setDisabled currently is the opposite)
01254 // DeselectOptionsByIndex(startIndex, endIndex, checkDisabled)
01255 //   startIndex, endIndex - the range of options to turn on
01256 //                          (-1, -1) will clear all indices no matter what.
01257 //   checkDisabled - if this is TRUE, and an option is disabled, it will not be
01258 //                   changed regardless of whether it is selected or not.
01259 //                   Generally the UI passes TRUE and JS passes FALSE.
01260 //                   (setDisabled currently is the opposite)
01261 NS_IMETHODIMP
01262 nsHTMLSelectElement::SetOptionsSelectedByIndex(PRInt32 aStartIndex,
01263                                                PRInt32 aEndIndex,
01264                                                PRBool aIsSelected,
01265                                                PRBool aClearAll,
01266                                                PRBool aSetDisabled,
01267                                                PRBool aNotify,
01268                                                PRBool* aChangedSomething)
01269 {
01270 #if 0
01271   printf("SetOption(%d-%d, %c, ClearAll=%c)\n", aStartIndex, aEndIndex,
01272                                        (aIsSelected ? 'Y' : 'N'),
01273                                        (aClearAll ? 'Y' : 'N'));
01274 #endif
01275   if (aChangedSomething) {
01276     *aChangedSomething = PR_FALSE;
01277   }
01278 
01279   nsresult rv;
01280 
01281   // Don't bother if the select is disabled
01282   if (!aSetDisabled) {
01283     PRBool selectIsDisabled = PR_FALSE;
01284     rv = GetDisabled(&selectIsDisabled);
01285     if (NS_SUCCEEDED(rv) && selectIsDisabled) {
01286       return NS_OK;
01287     }
01288   }
01289 
01290   // Don't bother if there are no options
01291   PRUint32 numItems = 0;
01292   GetLength(&numItems);
01293   if (numItems == 0) {
01294     return NS_OK;
01295   }
01296 
01297   // First, find out whether multiple items can be selected
01298   PRBool isMultiple;
01299   rv = GetMultiple(&isMultiple);
01300   if (NS_FAILED(rv)) {
01301     isMultiple = PR_FALSE;
01302   }
01303 
01304   // These variables tell us whether any options were selected
01305   // or deselected.
01306   PRBool optionsSelected = PR_FALSE;
01307   PRBool optionsDeselected = PR_FALSE;
01308 
01309   nsISelectControlFrame *selectFrame = nsnull;
01310   PRBool did_get_frame = PR_FALSE;
01311 
01312   nsPresContext *presContext = GetPresContext();
01313 
01314   if (aIsSelected) {
01315     // Only select the first value if it's not multiple
01316     if (!isMultiple) {
01317       aEndIndex = aStartIndex;
01318     }
01319 
01320     // This variable tells whether or not all of the options we attempted to
01321     // select are disabled.  If ClearAll is passed in as true, and we do not
01322     // select anything because the options are disabled, we will not clear the
01323     // other options.  (This is to make the UI work the way one might expect.)
01324     PRBool allDisabled = !aSetDisabled;
01325 
01326     //
01327     // Save a little time when clearing other options
01328     //
01329     PRInt32 previousSelectedIndex = mSelectedIndex;
01330 
01331     //
01332     // Select the requested indices
01333     //
01334     // If index is -1, everything will be deselected (bug 28143)
01335     if (aStartIndex != -1) {
01336       // Verify that the indices are within bounds
01337       if (aStartIndex >= (PRInt32)numItems || aStartIndex < 0
01338          || aEndIndex >= (PRInt32)numItems || aEndIndex < 0) {
01339         return NS_ERROR_FAILURE;
01340       }
01341 
01342       // Loop through the options and select them (if they are not disabled and
01343       // if they are not already selected).
01344       for (PRInt32 optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) {
01345 
01346         // Ignore disabled options.
01347         if (!aSetDisabled) {
01348           PRBool isDisabled;
01349           IsOptionDisabled(optIndex, &isDisabled);
01350 
01351           if (isDisabled) {
01352             continue;
01353           } else {
01354             allDisabled = PR_FALSE;
01355           }
01356         }
01357 
01358         nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
01359         if (option) {
01360           // If the index is already selected, ignore it.
01361           PRBool isSelected = PR_FALSE;
01362           option->GetSelected(&isSelected);
01363           if (!isSelected) {
01364             // To notify the frame if anything gets changed. No need
01365             // to flush here, if there's no frame yet we don't need to
01366             // force it to be created just to notify it about a change
01367             // in the select.
01368             selectFrame = GetSelectFrame();
01369 
01370             did_get_frame = PR_TRUE;
01371 
01372             OnOptionSelected(selectFrame, presContext, optIndex, PR_TRUE, aNotify);
01373             optionsSelected = PR_TRUE;
01374           }
01375         }
01376       }
01377     }
01378 
01379     // Next remove all other options if single select or all is clear
01380     // If index is -1, everything will be deselected (bug 28143)
01381     if (((!isMultiple && optionsSelected)
01382        || (aClearAll && !allDisabled)
01383        || aStartIndex == -1)
01384        && previousSelectedIndex != -1) {
01385       for (PRInt32 optIndex = previousSelectedIndex;
01386            optIndex < (PRInt32)numItems;
01387            optIndex++) {
01388         if (optIndex < aStartIndex || optIndex > aEndIndex) {
01389           nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
01390           if (option) {
01391             // If the index is already selected, ignore it.
01392             PRBool isSelected = PR_FALSE;
01393             option->GetSelected(&isSelected);
01394             if (isSelected) {
01395               if (!did_get_frame) {
01396                 // To notify the frame if anything gets changed, don't
01397                 // flush, if the frame doesn't exist we don't need to
01398                 // create it just to tell it about this change.
01399                 selectFrame = GetSelectFrame();
01400 
01401                 did_get_frame = PR_TRUE;
01402               }
01403 
01404               OnOptionSelected(selectFrame, presContext, optIndex, PR_FALSE, aNotify);
01405               optionsDeselected = PR_TRUE;
01406 
01407               // Only need to deselect one option if not multiple
01408               if (!isMultiple) {
01409                 break;
01410               }
01411             }
01412           }
01413         }
01414       }
01415     }
01416 
01417   } else {
01418 
01419     // If we're deselecting, loop through all selected items and deselect
01420     // any that are in the specified range.
01421     for (PRInt32 optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) {
01422       if (!aSetDisabled) {
01423         PRBool isDisabled;
01424         IsOptionDisabled(optIndex, &isDisabled);
01425         if (isDisabled) {
01426           continue;
01427         }
01428       }
01429 
01430       nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
01431       if (option) {
01432         // If the index is already selected, ignore it.
01433         PRBool isSelected = PR_FALSE;
01434         option->GetSelected(&isSelected);
01435         if (isSelected) {
01436           if (!did_get_frame) {
01437             // To notify the frame if anything gets changed, don't
01438             // flush, if the frame doesn't exist we don't need to
01439             // create it just to tell it about this change.
01440             selectFrame = GetSelectFrame();
01441 
01442             did_get_frame = PR_TRUE;
01443           }
01444 
01445           OnOptionSelected(selectFrame, presContext, optIndex, PR_FALSE, aNotify);
01446           optionsDeselected = PR_TRUE;
01447         }
01448       }
01449     }
01450   }
01451 
01452   // Make sure something is selected unless we were set to -1 (none)
01453   if (optionsDeselected && aStartIndex != -1) {
01454     optionsSelected = CheckSelectSomething() || optionsSelected;
01455   }
01456 
01457   // Let the caller know whether anything was changed
01458   if (optionsSelected || optionsDeselected) {
01459     if (aChangedSomething)
01460       *aChangedSomething = PR_TRUE;
01461 
01462     // Dispatch an event to notify the subcontent that the selected item has changed
01463     DispatchDOMEvent(NS_LITERAL_STRING("selectedItemChanged"));
01464   }
01465 
01466   return NS_OK;
01467 }
01468 
01469 NS_IMETHODIMP
01470 nsHTMLSelectElement::IsOptionDisabled(PRInt32 aIndex, PRBool* aIsDisabled)
01471 {
01472   *aIsDisabled = PR_FALSE;
01473   nsCOMPtr<nsIDOMNode> optionNode;
01474   Item(aIndex, getter_AddRefs(optionNode));
01475   NS_ENSURE_TRUE(optionNode, NS_ERROR_FAILURE);
01476 
01477   nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(optionNode);
01478   if (option) {
01479     PRBool isDisabled;
01480     option->GetDisabled(&isDisabled);
01481     if (isDisabled) {
01482       *aIsDisabled = PR_TRUE;
01483       return NS_OK;
01484     }
01485   }
01486 
01487   // Check for disabled optgroups
01488   // If there are no artifacts, there are no optgroups
01489   if (mNonOptionChildren) {
01490     nsCOMPtr<nsIDOMNode> parent;
01491     while (1) {
01492       optionNode->GetParentNode(getter_AddRefs(parent));
01493 
01494       // If we reached the top of the doc (scary), we're done
01495       if (!parent) {
01496         break;
01497       }
01498 
01499       // If we reached the select element, we're done
01500       nsCOMPtr<nsIDOMHTMLSelectElement> selectElement =
01501         do_QueryInterface(parent);
01502       if (selectElement) {
01503         break;
01504       }
01505 
01506       nsCOMPtr<nsIDOMHTMLOptGroupElement> optGroupElement =
01507         do_QueryInterface(parent);
01508 
01509       if (optGroupElement) {
01510         PRBool isDisabled;
01511         optGroupElement->GetDisabled(&isDisabled);
01512 
01513         if (isDisabled) {
01514           *aIsDisabled = PR_TRUE;
01515           return NS_OK;
01516         }
01517       } else {
01518         // If you put something else between you and the optgroup, you're a
01519         // moron and you deserve not to have optgroup disabling work.
01520         break;
01521       }
01522 
01523       optionNode = parent;
01524     }
01525   }
01526 
01527   return NS_OK;
01528 }
01529 
01530 NS_IMETHODIMP
01531 nsHTMLSelectElement::GetValue(nsAString& aValue)
01532 {
01533   PRInt32 selectedIndex;
01534 
01535   nsresult rv = GetSelectedIndex(&selectedIndex);
01536 
01537   if (NS_SUCCEEDED(rv) && selectedIndex > -1) {
01538     nsCOMPtr<nsIDOMNode> node;
01539 
01540     rv = Item(selectedIndex, getter_AddRefs(node));
01541 
01542     nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(node);
01543     if (NS_SUCCEEDED(rv) && option) {
01544       return option->GetValue(aValue);
01545     }
01546   }
01547 
01548   aValue.Truncate(0);
01549   return rv;
01550 }
01551 
01552 NS_IMETHODIMP
01553 nsHTMLSelectElement::SetValue(const nsAString& aValue)
01554 {
01555   nsresult rv = NS_OK;
01556 
01557   PRUint32 length;
01558   rv = GetLength(&length);
01559   if (NS_SUCCEEDED(rv)) {
01560     PRUint32 i;
01561     for (i = 0; i < length; i++) {
01562       nsCOMPtr<nsIDOMNode> node;
01563 
01564       rv = Item(i, getter_AddRefs(node));
01565 
01566       if (NS_SUCCEEDED(rv) && node) {
01567         nsCOMPtr<nsIDOMHTMLOptionElement> option = do_QueryInterface(node);
01568 
01569         if (option) {
01570           nsAutoString optionVal;
01571 
01572           option->GetValue(optionVal);
01573 
01574           if (optionVal.Equals(aValue)) {
01575             SetSelectedIndex((PRInt32)i);
01576 
01577             break;
01578           }
01579         }
01580       }
01581     }
01582   }
01583 
01584   return rv;
01585 }
01586 
01587 
01588 NS_IMPL_BOOL_ATTR(nsHTMLSelectElement, Disabled, disabled)
01589 NS_IMPL_BOOL_ATTR(nsHTMLSelectElement, Multiple, multiple)
01590 NS_IMPL_STRING_ATTR(nsHTMLSelectElement, Name, name)
01591 NS_IMPL_INT_ATTR_DEFAULT_VALUE(nsHTMLSelectElement, Size, size, 0)
01592 NS_IMPL_INT_ATTR_DEFAULT_VALUE(nsHTMLSelectElement, TabIndex, tabindex, 0)
01593 
01594 NS_IMETHODIMP
01595 nsHTMLSelectElement::Blur()
01596 {
01597   if (ShouldFocus(this)) {
01598     SetElementFocus(PR_FALSE);
01599   }
01600 
01601   return NS_OK;
01602 }
01603 
01604 NS_IMETHODIMP
01605 nsHTMLSelectElement::Focus()
01606 {
01607   if (ShouldFocus(this)) {
01608     SetElementFocus(PR_TRUE);
01609   }
01610 
01611   return NS_OK;
01612 }
01613 
01614 void
01615 nsHTMLSelectElement::SetFocus(nsPresContext* aPresContext)
01616 {
01617   if (!aPresContext)
01618     return;
01619 
01620   // first see if we are disabled or not. If disabled then do nothing.
01621   if (HasAttr(kNameSpaceID_None, nsHTMLAtoms::disabled)) {
01622     return;
01623   }
01624 
01625   aPresContext->EventStateManager()->SetContentState(this,
01626                                                      NS_EVENT_STATE_FOCUS);
01627 
01628   nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
01629 
01630   if (formControlFrame) {
01631     formControlFrame->SetFocus(PR_TRUE, PR_TRUE);
01632     formControlFrame->ScrollIntoView(aPresContext);
01633     // Could call SelectAll(aPresContext) here to automatically
01634     // select text when we receive focus.
01635   }
01636 }
01637 
01638 PRBool
01639 nsHTMLSelectElement::IsFocusable(PRInt32 *aTabIndex)
01640 {
01641   if (!nsGenericHTMLElement::IsFocusable(aTabIndex)) {
01642     return PR_FALSE;
01643   }
01644   if (aTabIndex && (sTabFocusModel & eTabFocus_formElementsMask) == 0) {
01645     *aTabIndex = -1;
01646   }
01647   return PR_TRUE;
01648 }
01649 
01650 NS_IMETHODIMP
01651 nsHTMLSelectElement::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
01652 {
01653   return mOptions->Item(aIndex, aReturn);
01654 }
01655 
01656 NS_IMETHODIMP
01657 nsHTMLSelectElement::NamedItem(const nsAString& aName,
01658                                nsIDOMNode** aReturn)
01659 {
01660   return mOptions->NamedItem(aName, aReturn);
01661 }
01662 
01663 PRBool
01664 nsHTMLSelectElement::CheckSelectSomething()
01665 {
01666   if (mIsDoneAddingChildren) {
01667     if (mSelectedIndex < 0 && IsCombobox()) {
01668       return SelectSomething();
01669     }
01670   }
01671   return PR_FALSE;
01672 }
01673 
01674 PRBool
01675 nsHTMLSelectElement::SelectSomething()
01676 {
01677   // If we're not done building the select, don't play with this yet.
01678   if (!mIsDoneAddingChildren) {
01679     return PR_FALSE;
01680   }
01681 
01682   PRUint32 count;
01683   GetLength(&count);
01684   for (PRUint32 i=0; i<count; i++) {
01685     PRBool disabled;
01686     nsresult rv = IsOptionDisabled(i, &disabled);
01687 
01688     if (NS_FAILED(rv) || !disabled) {
01689       rv = SetSelectedIndex(i);
01690       NS_ENSURE_SUCCESS(rv, PR_FALSE);
01691       return PR_TRUE;
01692     }
01693   }
01694 
01695   return PR_FALSE;
01696 }
01697 
01698 PRBool
01699 nsHTMLSelectElement::IsDoneAddingChildren()
01700 {
01701   return mIsDoneAddingChildren;
01702 }
01703 
01704 void
01705 nsHTMLSelectElement::DoneAddingChildren()
01706 {
01707   mIsDoneAddingChildren = PR_TRUE;
01708 
01709   nsISelectControlFrame* selectFrame = GetSelectFrame();
01710 
01711   // If we foolishly tried to restore before we were done adding
01712   // content, restore the rest of the options proper-like
01713   if (mRestoreState) {
01714     RestoreStateTo(mRestoreState);
01715     mRestoreState = nsnull;
01716   }
01717 
01718   // Notify the frame
01719   if (selectFrame) {
01720     selectFrame->DoneAddingChildren(PR_TRUE);
01721   }
01722 
01723   // Restore state
01724   RestoreFormControlState(this, this);
01725 
01726   // Now that we're done, select something (if it's a single select something
01727   // must be selected)
01728   CheckSelectSomething();
01729 }
01730 
01731 PRBool
01732 nsHTMLSelectElement::ParseAttribute(nsIAtom* aAttribute,
01733                                     const nsAString& aValue,
01734                                     nsAttrValue& aResult)
01735 {
01736   if (aAttribute == nsHTMLAtoms::size) {
01737     return aResult.ParseIntWithBounds(aValue, 0);
01738   }
01739   return nsGenericHTMLElement::ParseAttribute(aAttribute, aValue, aResult);
01740 }
01741 
01742 static void
01743 MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
01744                       nsRuleData* aData)
01745 {
01746   nsGenericHTMLFormElement::MapImageAlignAttributeInto(aAttributes, aData);
01747   nsGenericHTMLFormElement::MapCommonAttributesInto(aAttributes, aData);
01748 }
01749 
01750 nsChangeHint
01751 nsHTMLSelectElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
01752                                             PRInt32 aModType) const
01753 {
01754   nsChangeHint retval =
01755       nsGenericHTMLFormElement::GetAttributeChangeHint(aAttribute, aModType);
01756   if (aAttribute == nsHTMLAtoms::multiple ||
01757       aAttribute == nsHTMLAtoms::size) {
01758     NS_UpdateHint(retval, NS_STYLE_HINT_FRAMECHANGE);
01759   }
01760   return retval;
01761 }
01762 
01763 NS_IMETHODIMP_(PRBool)
01764 nsHTMLSelectElement::IsAttributeMapped(const nsIAtom* aAttribute) const
01765 {
01766   static const MappedAttributeEntry* const map[] = {
01767     sCommonAttributeMap,
01768     sImageAlignAttributeMap
01769   };
01770 
01771   return FindAttributeDependence(aAttribute, map, NS_ARRAY_LENGTH(map));
01772 }
01773 
01774 nsMapRuleToAttributesFunc
01775 nsHTMLSelectElement::GetAttributeMappingFunction() const
01776 {
01777   return &MapAttributesIntoRule;
01778 }
01779 
01780 
01781 nsresult
01782 nsHTMLSelectElement::HandleDOMEvent(nsPresContext* aPresContext,
01783                                     nsEvent* aEvent,
01784                                     nsIDOMEvent** aDOMEvent,
01785                                     PRUint32 aFlags,
01786                                     nsEventStatus* aEventStatus)
01787 {
01788   // Do not process any DOM events if the element is disabled
01789   PRBool disabled;
01790   nsresult rv = GetDisabled(&disabled);
01791   if (NS_FAILED(rv) || disabled) {
01792     return rv;
01793   }
01794 
01795   nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_FALSE);
01796   nsIFrame* formFrame = nsnull;
01797 
01798   if (formControlFrame &&
01799       NS_SUCCEEDED(CallQueryInterface(formControlFrame, &formFrame)) &&
01800       formFrame)
01801   {
01802     const nsStyleUserInterface* uiStyle = formFrame->GetStyleUserInterface();
01803 
01804     if (uiStyle->mUserInput == NS_STYLE_USER_INPUT_NONE ||
01805         uiStyle->mUserInput == NS_STYLE_USER_INPUT_DISABLED) {
01806       return NS_OK;
01807     }
01808   }
01809 
01810   // Must notify the frame that the blur event occurred
01811   // NOTE: At this point EventStateManager has not yet set the
01813   // the focused element. So the ComboboxControlFrame tracks the focus
01814   // at a class level (Bug 32920)
01815   if ((nsEventStatus_eIgnore == *aEventStatus) &&
01816       !(aFlags & NS_EVENT_FLAG_CAPTURE) && !(aFlags & NS_EVENT_FLAG_SYSTEM_EVENT) &&
01817       (aEvent->message == NS_BLUR_CONTENT) && formControlFrame) {
01818     formControlFrame->SetFocus(PR_FALSE, PR_TRUE);
01819   }
01820 
01821   return nsGenericHTMLFormElement::HandleDOMEvent(aPresContext, aEvent,
01822                                                   aDOMEvent, aFlags,
01823                                                   aEventStatus);
01824 }
01825 
01826 // nsIFormControl
01827 
01828 NS_IMETHODIMP
01829 nsHTMLSelectElement::SaveState()
01830 {
01831   nsRefPtr<nsSelectState> state = new nsSelectState();
01832   if (!state) {
01833     return NS_ERROR_OUT_OF_MEMORY;
01834   }
01835 
01836   PRUint32 len;
01837   GetLength(&len);
01838 
01839   for (PRUint32 optIndex = 0; optIndex < len; optIndex++) {
01840     nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
01841     if (option) {
01842       PRBool isSelected;
01843       option->GetSelected(&isSelected);
01844       if (isSelected) {
01845         nsAutoString value;
01846         option->GetValue(value);
01847         state->PutOption(optIndex, value);
01848       }
01849     }
01850   }
01851 
01852   nsPresState *presState = nsnull;
01853   nsresult rv = GetPrimaryPresState(this, &presState);
01854   if (presState) {
01855     rv = presState->SetStatePropertyAsSupports(NS_LITERAL_STRING("selecteditems"),
01856                                            state);
01857     NS_ASSERTION(NS_SUCCEEDED(rv), "selecteditems set failed!");
01858   }
01859 
01860   return rv;
01861 }
01862 
01863 PRBool
01864 nsHTMLSelectElement::RestoreState(nsPresState* aState)
01865 {
01866   // Get the presentation state object to retrieve our stuff out of.
01867   nsCOMPtr<nsISupports> state;
01868   nsresult rv = aState->GetStatePropertyAsSupports(NS_LITERAL_STRING("selecteditems"),
01869                                                    getter_AddRefs(state));
01870   if (NS_SUCCEEDED(rv)) {
01871     RestoreStateTo((nsSelectState*)(nsISupports*)state);
01872 
01873     // Don't flush, if the frame doesn't exist yet it doesn't care if
01874     // we're reset or not.
01875     nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_FALSE);
01876     if (formControlFrame) {
01877       formControlFrame->OnContentReset();
01878     }
01879   }
01880 
01881   return PR_FALSE;
01882 }
01883 
01884 NS_IMETHODIMP
01885 nsHTMLSelectElement::GetBoxObject(nsIBoxObject** aResult)
01886 {
01887   *aResult = nsnull;
01888 
01889   nsCOMPtr<nsIDOMNSDocument> nsDoc = do_QueryInterface(GetCurrentDoc());
01890   if (!nsDoc) {
01891     return NS_ERROR_FAILURE;
01892   }
01893 
01894   return nsDoc->GetBoxObjectFor(NS_STATIC_CAST(nsIDOMElement*, this), aResult);
01895 }
01896 
01897 void
01898 nsHTMLSelectElement::RestoreStateTo(nsSelectState* aNewSelected)
01899 {
01900   if (!mIsDoneAddingChildren) {
01901     mRestoreState = aNewSelected;
01902     return;
01903   }
01904 
01905   PRUint32 len;
01906   GetLength(&len);
01907 
01908   // First clear all
01909   SetOptionsSelectedByIndex(-1, -1, PR_TRUE, PR_TRUE, PR_TRUE, PR_TRUE, nsnull);
01910 
01911   // Next set the proper ones
01912   for (PRInt32 i = 0; i < (PRInt32)len; i++) {
01913     nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(i);
01914     if (option) {
01915       nsAutoString value;
01916       option->GetValue(value);
01917       if (aNewSelected->ContainsOption(i, value)) {
01918         SetOptionsSelectedByIndex(i, i, PR_TRUE, PR_FALSE, PR_TRUE, PR_TRUE, nsnull);
01919       }
01920     }
01921   }
01922 
01923   //CheckSelectSomething();
01924 }
01925 
01926 NS_IMETHODIMP
01927 nsHTMLSelectElement::Reset()
01928 {
01929   PRUint32 numSelected = 0;
01930 
01931   //
01932   // Cycle through the options array and reset the options
01933   //
01934   PRUint32 numOptions;
01935   nsresult rv = GetLength(&numOptions);
01936   NS_ENSURE_SUCCESS(rv, rv);
01937 
01938   for (PRUint32 i = 0; i < numOptions; i++) {
01939     nsCOMPtr<nsIDOMNode> node;
01940     rv = Item(i, getter_AddRefs(node));
01941     NS_ENSURE_SUCCESS(rv, rv);
01942 
01943     nsCOMPtr<nsIDOMHTMLOptionElement> option(do_QueryInterface(node));
01944 
01945     NS_ASSERTION(option, "option not an OptionElement");
01946     if (option) {
01947       //
01948       // Reset the option to its default value
01949       //
01950       PRBool selected = PR_FALSE;
01951       option->GetDefaultSelected(&selected);
01952       SetOptionsSelectedByIndex(i, i, selected,
01953                                 PR_FALSE, PR_TRUE, PR_TRUE, nsnull);
01954       if (selected) {
01955         numSelected++;
01956       }
01957     }
01958   }
01959 
01960   //
01961   // If nothing was selected and it's not multiple, select something
01962   //
01963   if (numSelected == 0 && IsCombobox()) {
01964     SelectSomething();
01965   }
01966 
01967   //
01968   // Let the frame know we were reset
01969   //
01970   // Don't flush, if there's no frame yet it won't care about us being
01971   // reset even if we forced it to be created now.
01972   //
01973   nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_FALSE);
01974   if (formControlFrame) {
01975     formControlFrame->OnContentReset();
01976   }
01977 
01978   return NS_OK;
01979 }
01980 
01981 NS_IMETHODIMP
01982 nsHTMLSelectElement::SubmitNamesValues(nsIFormSubmission* aFormSubmission,
01983                                        nsIContent* aSubmitElement)
01984 {
01985   nsresult rv = NS_OK;
01986 
01987   //
01988   // Disabled elements don't submit
01989   //
01990   PRBool disabled;
01991   rv = GetDisabled(&disabled);
01992   if (NS_FAILED(rv) || disabled) {
01993     return rv;
01994   }
01995 
01996   //
01997   // Get the name (if no name, no submit)
01998   //
01999   nsAutoString name;
02000   rv = GetAttr(kNameSpaceID_None, nsHTMLAtoms::name, name);
02001   if (NS_FAILED(rv) || rv == NS_CONTENT_ATTR_NOT_THERE) {
02002     return rv;
02003   }
02004 
02005   //
02006   // Submit
02007   //
02008   PRUint32 len;
02009   GetLength(&len);
02010 
02011   for (PRUint32 optIndex = 0; optIndex < len; optIndex++) {
02012     // Don't send disabled options
02013     PRBool disabled;
02014     rv = IsOptionDisabled(optIndex, &disabled);
02015     if (NS_FAILED(rv) || disabled) {
02016       continue;
02017     }
02018 
02019     nsIDOMHTMLOptionElement *option = mOptions->ItemAsOption(optIndex);
02020     NS_ENSURE_TRUE(option, NS_ERROR_UNEXPECTED);
02021 
02022     PRBool isSelected;
02023     rv = option->GetSelected(&isSelected);
02024     NS_ENSURE_SUCCESS(rv, rv);
02025     if (!isSelected) {
02026       continue;
02027     }
02028 
02029     nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = do_QueryInterface(option);
02030     NS_ENSURE_TRUE(optionElement, NS_ERROR_UNEXPECTED);
02031 
02032     nsAutoString value;
02033     rv = optionElement->GetValue(value);
02034     NS_ENSURE_SUCCESS(rv, rv);
02035 
02036     rv = aFormSubmission->AddNameValuePair(this, name, value);
02037   }
02038 
02039   return NS_OK;
02040 }
02041 
02042 NS_IMETHODIMP
02043 nsHTMLSelectElement::GetHasOptGroups(PRBool* aHasGroups)
02044 {
02045   *aHasGroups = (mOptGroupCount > 0);
02046   return NS_OK;
02047 }
02048 
02049 void
02050 nsHTMLSelectElement::DispatchDOMEvent(const nsAString& aName)
02051 {
02052   nsCOMPtr<nsIDOMDocumentEvent> domDoc = do_QueryInterface(GetOwnerDoc());
02053   if (domDoc) {
02054     nsCOMPtr<nsIDOMEvent> selectEvent;
02055     domDoc->CreateEvent(NS_LITERAL_STRING("Events"),
02056                         getter_AddRefs(selectEvent));
02057 
02058     nsCOMPtr<nsIPrivateDOMEvent> privateEvent(do_QueryInterface(selectEvent));
02059 
02060     if (privateEvent) {
02061       selectEvent->InitEvent(aName, PR_TRUE, PR_TRUE);
02062       privateEvent->SetTrusted(PR_TRUE);
02063 
02064       nsCOMPtr<nsIDOMEventTarget> target =
02065         do_QueryInterface(NS_STATIC_CAST(nsIDOMNode*, this));
02066       PRBool defaultActionEnabled;
02067       target->DispatchEvent(selectEvent, &defaultActionEnabled);
02068     }
02069   }
02070 }
02071 
02072 static void
02073 AddOptionsRecurse(nsIContent* aRoot, nsHTMLOptionCollection* aArray)
02074 {
02075   nsIContent* child;
02076   for(PRUint32 i = 0; (child = aRoot->GetChildAt(i)); ++i) {
02077     nsCOMPtr<nsIDOMHTMLOptionElement> opt = do_QueryInterface(child);
02078     if (opt) {
02079       // If we fail here, then at least we've tried our best
02080       aArray->AppendOption(opt);
02081     }
02082     else if (IsOptGroup(child)) {
02083       AddOptionsRecurse(child, aArray);
02084     }
02085   }
02086 }
02087 
02088 void
02089 nsHTMLSelectElement::RebuildOptionsArray()
02090 {
02091   mOptions->Clear();
02092   AddOptionsRecurse(this, mOptions);
02093 }
02094 
02095 //----------------------------------------------------------------------
02096 //
02097 // nsHTMLOptionCollection implementation
02098 //
02099 
02100 nsHTMLOptionCollection::nsHTMLOptionCollection(nsHTMLSelectElement* aSelect)
02101 {
02102   // Do not maintain a reference counted reference. When
02103   // the select goes away, it will let us know.
02104   mSelect = aSelect;
02105 }
02106 
02107 nsHTMLOptionCollection::~nsHTMLOptionCollection()
02108 {
02109   DropReference();
02110 }
02111 
02112 void
02113 nsHTMLOptionCollection::DropReference()
02114 {
02115   // Drop our (non ref-counted) reference
02116   mSelect = nsnull;
02117 }
02118 
02119 nsresult
02120 nsHTMLOptionCollection::GetOptionIndex(nsIDOMHTMLOptionElement* aOption,
02121                                        PRInt32 aStartIndex,
02122                                        PRBool aForward,
02123                                        PRInt32* aIndex)
02124 {
02125   PRInt32 index;
02126 
02127   // Make the common case fast
02128   if (aStartIndex == 0 && aForward) {
02129     index = mElements.IndexOf(aOption);
02130     if (index == -1) {
02131       return NS_ERROR_FAILURE;
02132     }
02133     
02134     *aIndex = index;
02135     return NS_OK;
02136   }
02137 
02138   PRInt32 high = mElements.Count();
02139   PRInt32 step = aForward ? 1 : -1;
02140 
02141   for (index = aStartIndex; index < high && index > -1; index += step) {
02142     if (mElements[index] == aOption) {
02143       *aIndex = index;
02144       return NS_OK;
02145     }
02146   }
02147 
02148   return NS_ERROR_FAILURE;
02149 }
02150 
02151 
02152 // nsISupports
02153 
02154 // QueryInterface implementation for nsHTMLOptionCollection
02155 NS_INTERFACE_MAP_BEGIN(nsHTMLOptionCollection)
02156   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLOptionsCollection)
02157   NS_INTERFACE_MAP_ENTRY(nsIDOMNSHTMLOptionCollection)
02158   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLCollection)
02159   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMNSHTMLOptionCollection)
02160   NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(HTMLOptionsCollection)
02161 NS_INTERFACE_MAP_END
02162 
02163 
02164 NS_IMPL_ADDREF_INHERITED(nsHTMLOptionCollection, nsGenericDOMHTMLCollection)
02165 NS_IMPL_RELEASE_INHERITED(nsHTMLOptionCollection, nsGenericDOMHTMLCollection)
02166 
02167 
02168 // nsIDOMNSHTMLOptionCollection interface
02169 
02170 NS_IMETHODIMP
02171 nsHTMLOptionCollection::GetLength(PRUint32* aLength)
02172 {
02173   *aLength = mElements.Count();
02174 
02175   return NS_OK;
02176 }
02177 
02178 NS_IMETHODIMP
02179 nsHTMLOptionCollection::SetLength(PRUint32 aLength)
02180 {
02181   if (!mSelect) {
02182     return NS_ERROR_UNEXPECTED;
02183   }
02184 
02185   return mSelect->SetLength(aLength);
02186 }
02187 
02188 NS_IMETHODIMP
02189 nsHTMLOptionCollection::SetOption(PRInt32 aIndex,
02190                                   nsIDOMHTMLOptionElement *aOption)
02191 {
02192   if (aIndex < 0 || !mSelect) {
02193     return NS_OK;
02194   }
02195   
02196   // if the new option is null, just remove this option.  Note that it's safe
02197   // to pass a too-large aIndex in here.
02198   if (!aOption) {
02199     mSelect->Remove(aIndex);
02200 
02201     // We're done.
02202     return NS_OK;
02203   }
02204 
02205   nsresult rv = NS_OK;
02206 
02207   // Now we're going to be setting an option in our collection
02208   if (aIndex > mElements.Count()) {
02209     // Fill our array with blank options up to (but not including, since we're
02210     // about to change it) aIndex, for compat with other browsers.
02211     rv = SetLength(aIndex);
02212     NS_ENSURE_SUCCESS(rv, rv);
02213   }
02214 
02215   NS_ASSERTION(aIndex <= mElements.Count(), "SetLength lied");
02216   
02217   nsCOMPtr<nsIDOMNode> ret;
02218   if (aIndex == mElements.Count()) {
02219     rv = mSelect->AppendChild(aOption, getter_AddRefs(ret));
02220   } else {
02221     // Find the option they're talking about and replace it
02222     // hold a strong reference to follow COM rules.
02223     nsCOMPtr<nsIDOMHTMLOptionElement> refChild = mElements.SafeObjectAt(aIndex);
02224     NS_ENSURE_TRUE(refChild, NS_ERROR_UNEXPECTED);
02225 
02226     nsCOMPtr<nsIDOMNode> parent;
02227     refChild->GetParentNode(getter_AddRefs(parent));
02228     if (parent) {
02229       rv = parent->ReplaceChild(aOption, refChild, getter_AddRefs(ret));
02230     }
02231   }
02232 
02233   return rv;
02234 }
02235 
02236 NS_IMETHODIMP
02237 nsHTMLOptionCollection::GetSelectedIndex(PRInt32 *aSelectedIndex)
02238 {
02239   NS_ENSURE_TRUE(mSelect, NS_ERROR_UNEXPECTED);
02240 
02241   return mSelect->GetSelectedIndex(aSelectedIndex);
02242 }
02243 
02244 NS_IMETHODIMP
02245 nsHTMLOptionCollection::SetSelectedIndex(PRInt32 aSelectedIndex)
02246 {
02247   NS_ENSURE_TRUE(mSelect, NS_ERROR_UNEXPECTED);
02248 
02249   return mSelect->SetSelectedIndex(aSelectedIndex);
02250 }
02251 
02252 NS_IMETHODIMP
02253 nsHTMLOptionCollection::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
02254 {
02255   nsIDOMHTMLOptionElement *option = mElements.SafeObjectAt(aIndex);
02256 
02257   NS_IF_ADDREF(*aReturn = option);
02258 
02259   return NS_OK;
02260 }
02261 
02262 NS_IMETHODIMP
02263 nsHTMLOptionCollection::NamedItem(const nsAString& aName,
02264                                   nsIDOMNode** aReturn)
02265 {
02266   PRInt32 count = mElements.Count();
02267   nsresult rv = NS_OK;
02268 
02269   *aReturn = nsnull;
02270 
02271   for (PRInt32 i = 0; i < count; i++) {
02272     nsCOMPtr<nsIContent> content = do_QueryInterface(mElements.ObjectAt(i));
02273 
02274     if (content) {
02275       nsAutoString name;
02276 
02277       if (((content->GetAttr(kNameSpaceID_None, nsHTMLAtoms::name,
02278                              name) == NS_CONTENT_ATTR_HAS_VALUE) &&
02279            aName.Equals(name)) ||
02280           ((content->GetAttr(kNameSpaceID_None, nsHTMLAtoms::id,
02281                              name) == NS_CONTENT_ATTR_HAS_VALUE) &&
02282            aName.Equals(name))) {
02283         rv = CallQueryInterface(content, aReturn);
02284 
02285         break;
02286       }
02287     }
02288   }
02289 
02290   return rv;
02291 }
02292 
02293 NS_IMETHODIMP
02294 nsHTMLOptionCollection::GetSelect(nsIDOMHTMLSelectElement **aReturn)
02295 {
02296   NS_IF_ADDREF(*aReturn = mSelect);
02297   return NS_OK;
02298 }