From c85d34bbe313f520f911d56b7c8a671539614be0 Mon Sep 17 00:00:00 2001 From: "Olli.Pettay%helsinki.fi" Date: Tue, 28 Aug 2007 07:09:32 +0000 Subject: [PATCH] Bug 339501, r=bz, sr=sicking, a=1.9+ git-svn-id: svn://10.0.0.236/trunk@233195 18797224-902f-48f8-a5cc-f745e15eee43 --- .../content/src/nsHTMLOptGroupElement.cpp | 21 +- .../html/content/src/nsHTMLSelectElement.cpp | 495 ++--------------- .../html/content/src/nsHTMLSelectElement.h | 496 ++++++++++++++++++ 3 files changed, 565 insertions(+), 447 deletions(-) create mode 100644 mozilla/content/html/content/src/nsHTMLSelectElement.h diff --git a/mozilla/content/html/content/src/nsHTMLOptGroupElement.cpp b/mozilla/content/html/content/src/nsHTMLOptGroupElement.cpp index 2fc65ec37d2..dd4ec6c64d0 100644 --- a/mozilla/content/html/content/src/nsHTMLOptGroupElement.cpp +++ b/mozilla/content/html/content/src/nsHTMLOptGroupElement.cpp @@ -48,6 +48,7 @@ #include "nsISelectElement.h" #include "nsIDOMHTMLSelectElement.h" #include "nsEventDispatcher.h" +#include "nsHTMLSelectElement.h" /** * The implementation of <optgroup> @@ -174,23 +175,23 @@ nsHTMLOptGroupElement::InsertChildAt(nsIContent* aKid, PRUint32 aIndex, PRBool aNotify) { - nsCOMPtr sel = do_QueryInterface(GetSelect()); - if (sel) { - sel->WillAddOptions(aKid, this, aIndex); + nsSafeOptionListMutation safeMutation(GetSelect(), this, aKid, aIndex); + nsresult rv = nsGenericHTMLElement::InsertChildAt(aKid, aIndex, aNotify); + if (NS_FAILED(rv)) { + safeMutation.MutationFailed(); } - - return nsGenericHTMLElement::InsertChildAt(aKid, aIndex, aNotify); + return rv; } nsresult nsHTMLOptGroupElement::RemoveChildAt(PRUint32 aIndex, PRBool aNotify) { - nsCOMPtr sel = do_QueryInterface(GetSelect()); - if (sel) { - sel->WillRemoveOptions(this, aIndex); + nsSafeOptionListMutation safeMutation(GetSelect(), this, nsnull, aIndex); + nsresult rv = nsGenericHTMLElement::RemoveChildAt(aIndex, aNotify); + if (NS_FAILED(rv)) { + safeMutation.MutationFailed(); } - - return nsGenericHTMLElement::RemoveChildAt(aIndex, aNotify); + return rv; } PRInt32 diff --git a/mozilla/content/html/content/src/nsHTMLSelectElement.cpp b/mozilla/content/html/content/src/nsHTMLSelectElement.cpp index 5c56f1f9cc6..1645757cbf8 100644 --- a/mozilla/content/html/content/src/nsHTMLSelectElement.cpp +++ b/mozilla/content/html/content/src/nsHTMLSelectElement.cpp @@ -36,15 +36,10 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -#include "nsCOMPtr.h" -#include "nsCOMArray.h" -#include "nsIDOMHTMLSelectElement.h" -#include "nsIDOMNSHTMLSelectElement.h" -#include "nsIDOMNSXBLFormControl.h" -#include "nsIDOMHTMLFormElement.h" + +#include "nsHTMLSelectElement.h" #include "nsIDOMEventTarget.h" #include "nsContentCreatorFunctions.h" -#include "nsGenericHTMLElement.h" #include "nsGkAtoms.h" #include "nsStyleConsts.h" #include "nsPresContext.h" @@ -52,28 +47,16 @@ #include "nsMappedAttributes.h" #include "nsIForm.h" #include "nsIFormSubmission.h" -#include "nsIDOMHTMLCollection.h" -#include "nsIDOMHTMLOptionElement.h" + #include "nsIDOMHTMLOptGroupElement.h" #include "nsIOptionElement.h" #include "nsIEventStateManager.h" -#include "nsISelectElement.h" -#include "nsISelectControlFrame.h" -#include "nsIDOMHTMLOptionsCollection.h" -#include "nsIDOMNSHTMLOptionCollectn.h" #include "nsGUIEvent.h" #include "nsIPrivateDOMEvent.h" #include "nsIBoxObject.h" #include "nsIDOMNSDocument.h" #include "nsIDOMDocumentEvent.h" -// PresState -#include "nsXPCOM.h" -#include "nsPresState.h" -#include "nsIComponentManager.h" -#include "nsCheapSets.h" -#include "nsLayoutErrors.h" - // Notify/query select frame for selectedIndex #include "nsIDocument.h" #include "nsIPresShell.h" @@ -86,405 +69,58 @@ #include "nsRuleData.h" #include "nsEventDispatcher.h" -class nsHTMLSelectElement; - -/** - * The collection of options in the select (what you get back when you do - * select.options in DOM) - */ -class nsHTMLOptionCollection: public nsIDOMHTMLOptionsCollection, - public nsIDOMNSHTMLOptionCollection, - public nsIDOMHTMLCollection -{ -public: - nsHTMLOptionCollection(nsHTMLSelectElement* aSelect); - virtual ~nsHTMLOptionCollection(); - - NS_DECL_CYCLE_COLLECTING_ISUPPORTS - - // nsIDOMHTMLOptionsCollection interface - NS_DECL_NSIDOMHTMLOPTIONSCOLLECTION - - // nsIDOMNSHTMLOptionCollection interface - NS_DECL_NSIDOMNSHTMLOPTIONCOLLECTION - - // nsIDOMHTMLCollection interface, all its methods are defined in - // nsIDOMHTMLOptionsCollection - - NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHTMLOptionCollection, - nsIDOMNSHTMLOptionCollection) - - // Helpers for nsHTMLSelectElement - /** - * Insert an option - * @param aOption the option to insert - * @param aIndex the index to insert at - */ - PRBool InsertOptionAt(nsIDOMHTMLOptionElement* aOption, PRInt32 aIndex) - { - return mElements.InsertObjectAt(aOption, aIndex); - } - - /** - * Remove an option - * @param aIndex the index of the option to remove - */ - void RemoveOptionAt(PRInt32 aIndex) - { - mElements.RemoveObjectAt(aIndex); - } - - /** - * Get the option at the index - * @param aIndex the index - * @param aReturn the option returned [OUT] - */ - nsIDOMHTMLOptionElement *ItemAsOption(PRInt32 aIndex) - { - return mElements.SafeObjectAt(aIndex); - } - - /** - * Clears out all options - */ - void Clear() - { - mElements.Clear(); - } - - /** - * Append an option to end of array - */ - PRBool AppendOption(nsIDOMHTMLOptionElement* aOption) - { - return mElements.AppendObject(aOption); - } - - /** - * Drop the reference to the select. Called during select destruction. - */ - void DropReference(); - - /** - * See nsISelectElement.idl for documentation on this method - */ - nsresult GetOptionIndex(nsIDOMHTMLOptionElement* aOption, - PRInt32 aStartIndex, PRBool aForward, - PRInt32* aIndex); - -private: - /** The list of options (holds strong references) */ - nsCOMArray mElements; - /** The select element that contains this array */ - nsHTMLSelectElement* mSelect; -}; - - -/** - * The restore state used by select - */ -class nsSelectState : public nsISupports { -public: - nsSelectState() - { - } - virtual ~nsSelectState() - { - } - - NS_DECL_ISUPPORTS - - void PutOption(PRInt32 aIndex, const nsAString& aValue) - { - // If the option is empty, store the index. If not, store the value. - if (aValue.IsEmpty()) { - mIndices.Put(aIndex); - } else { - mValues.Put(aValue); - } - } - - PRBool ContainsOption(PRInt32 aIndex, const nsAString& aValue) - { - return mValues.Contains(aValue) || mIndices.Contains(aIndex); - } - -private: - nsCheapStringSet mValues; - nsCheapInt32Set mIndices; -}; - NS_IMPL_ISUPPORTS0(nsSelectState) +//---------------------------------------------------------------------- +// +// nsSafeOptionListMutation +// -/** - * Implementation of <select> - */ -class nsHTMLSelectElement : public nsGenericHTMLFormElement, - public nsIDOMHTMLSelectElement, - public nsIDOMNSHTMLSelectElement, - public nsIDOMNSXBLFormControl, - public nsISelectElement +nsSafeOptionListMutation::nsSafeOptionListMutation(nsIContent* aSelect, + nsIContent* aParent, + nsIContent* aKid, + PRUint32 aIndex) + : mSelect(do_QueryInterface(aSelect)), mTopLevelMutation(PR_FALSE), + mNeedsRebuild(PR_FALSE) { -public: - nsHTMLSelectElement(nsINodeInfo *aNodeInfo, PRBool aFromParser = PR_FALSE); - virtual ~nsHTMLSelectElement(); - - // nsISupports - NS_DECL_ISUPPORTS_INHERITED - - // nsIDOMNode - NS_FORWARD_NSIDOMNODE(nsGenericHTMLFormElement::) - - // nsIDOMElement - NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLFormElement::) - - // nsIDOMHTMLElement - NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLFormElement::) - - // nsIDOMHTMLSelectElement - NS_DECL_NSIDOMHTMLSELECTELEMENT - - // nsIDOMNSHTMLSelectElement - NS_DECL_NSIDOMNSHTMLSELECTELEMENT - - // nsIDOMNSXBLFormControl - NS_DECL_NSIDOMNSXBLFORMCONTROL - - // nsIContent - virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor); - - virtual void SetFocus(nsPresContext* aPresContext); - virtual PRBool IsFocusable(PRInt32 *aTabIndex = nsnull); - virtual nsresult InsertChildAt(nsIContent* aKid, PRUint32 aIndex, - PRBool aNotify); - virtual nsresult RemoveChildAt(PRUint32 aIndex, PRBool aNotify); - - // Overriden nsIFormControl methods - NS_IMETHOD_(PRInt32) GetType() const { return NS_FORM_SELECT; } - NS_IMETHOD Reset(); - NS_IMETHOD SubmitNamesValues(nsIFormSubmission* aFormSubmission, - nsIContent* aSubmitElement); - NS_IMETHOD SaveState(); - virtual PRBool RestoreState(nsPresState* aState); - - // nsISelectElement - NS_DECL_NSISELECTELEMENT - - /** - * Called when an attribute is about to be changed - */ - virtual nsresult BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, - const nsAString* aValue, PRBool aNotify); - virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute, - PRBool aNotify); - - virtual nsresult DoneAddingChildren(PRBool aHaveNotified); - virtual PRBool IsDoneAddingChildren(); - - virtual PRBool ParseAttribute(PRInt32 aNamespaceID, - nsIAtom* aAttribute, - const nsAString& aValue, - nsAttrValue& aResult); - virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const; - virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute, - PRInt32 aModType) const; - NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* aAttribute) const; - - virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const; - - NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLSelectElement, - nsGenericHTMLFormElement) - -protected: - // Helper Methods - /** - * Check whether the option specified by the index is selected - * @param aIndex the index - * @return whether the option at the index is selected - */ - PRBool IsOptionSelectedByIndex(PRInt32 aIndex); - /** - * Starting with (and including) aStartIndex, find the first selected index - * and set mSelectedIndex to it. - * @param aStartIndex the index to start with - */ - void FindSelectedIndex(PRInt32 aStartIndex); - /** - * Select some option if possible (generally the first non-disabled option). - * @return true if something was selected, false otherwise - */ - PRBool SelectSomething(); - /** - * Call SelectSomething(), but only if nothing is selected - * @see SelectSomething() - * @return true if something was selected, false otherwise - */ - PRBool CheckSelectSomething(); - /** - * Called to trigger notifications of frames and fixing selected index - * - * @param aSelectFrame the frame for this content (could be null) - * @param aPresContext the current pres context - * @param aIndex the index that was selected or deselected - * @param aSelected whether the index was selected or deselected - * @param aNotify whether to notify the style system and such - */ - void OnOptionSelected(nsISelectControlFrame* aSelectFrame, - nsPresContext* aPresContext, - PRInt32 aIndex, - PRBool aSelected, - PRBool aNotify); - /** - * Restore state to a particular state string (representing the options) - * @param aNewSelected the state string to restore to - */ - void RestoreStateTo(nsSelectState* aNewSelected); - -#ifdef DEBUG_john - // Don't remove these, por favor. They're very useful in debugging - nsresult PrintOptions(nsIContent* aOptions, PRInt32 tabs); -#endif - - // Adding options - /** - * Insert option(s) into the options[] array and perform notifications - * @param aOptions the option or optgroup being added - * @param aListIndex the index to start adding options into the list at - * @param aDepth the depth of aOptions (1=direct child of select ...) - */ - nsresult InsertOptionsIntoList(nsIContent* aOptions, - PRInt32 aListIndex, - PRInt32 aDepth); - /** - * Remove option(s) from the options[] array - * @param aOptions the option or optgroup being added - * @param aListIndex the index to start removing options from the list at - * @param aDepth the depth of aOptions (1=direct child of select ...) - */ - nsresult RemoveOptionsFromList(nsIContent* aOptions, - PRInt32 aListIndex, - PRInt32 aDepth); - /** - * Insert option(s) into the options[] array (called by InsertOptionsIntoList) - * @param aOptions the option or optgroup being added - * @param aInsertIndex the index to start adding options into the list at - * @param aDepth the depth of aOptions (1=direct child of select ...) - */ - nsresult InsertOptionsIntoListRecurse(nsIContent* aOptions, - PRInt32* aInsertIndex, - PRInt32 aDepth); - /** - * Remove option(s) from the options[] array (called by RemoveOptionsFromList) - * @param aOptions the option or optgroup being added - * @param aListIndex the index to start removing options from the list at - * @param aNumRemoved the number removed so far [OUT] - * @param aDepth the depth of aOptions (1=direct child of select ...) - */ - nsresult RemoveOptionsFromListRecurse(nsIContent* aOptions, - PRInt32 aRemoveIndex, - PRInt32* aNumRemoved, - PRInt32 aDepth); - /** - * Find out how deep this content is from the select (1=direct child) - * @param aContent the content to check - * @return the depth - */ - PRInt32 GetContentDepth(nsIContent* aContent); - /** - * Get the index of the first option at, under or following the content in - * the select, or length of options[] if none are found - * @param aOptions the content - * @return the index of the first option - */ - PRInt32 GetOptionIndexAt(nsIContent* aOptions); - /** - * Get the next option following the content in question (not at or under) - * (this could include siblings of the current content or siblings of the - * parent or children of siblings of the parent). - * @param aOptions the content - * @return the index of the next option after the content - */ - PRInt32 GetOptionIndexAfter(nsIContent* aOptions); - /** - * Get the first option index at or under the content in question. - * @param aOptions the content - * @return the index of the first option at or under the content - */ - PRInt32 GetFirstOptionIndex(nsIContent* aOptions); - /** - * Get the first option index under the content in question, within the - * range specified. - * @param aOptions the content - * @param aStartIndex the first child to look at - * @param aEndIndex the child *after* the last child to look at - * @return the index of the first option at or under the content - */ - PRInt32 GetFirstChildOptionIndex(nsIContent* aOptions, - PRInt32 aStartIndex, - PRInt32 aEndIndex); - - /** - * Get the frame as an nsISelectControlFrame (MAY RETURN NULL) - * @return the select frame, or null - */ - nsISelectControlFrame *GetSelectFrame(); - - /** - * Helper method for dispatching custom DOM events to our anonymous subcontent - * (for XBL form controls) - * @param aName the name of the event to dispatch - */ - void DispatchDOMEvent(const nsAString& aName); - - /** - * Is this a combobox? - */ - PRBool IsCombobox() { - PRBool isMultiple = PR_TRUE; - PRInt32 size = 1; - GetSize(&size); - GetMultiple(&isMultiple); - return !isMultiple && size <= 1; + nsHTMLSelectElement* select = static_cast(mSelect.get()); + if (select) { + mTopLevelMutation = !select->mMutating; + if (mTopLevelMutation) { + select->mMutating = PR_TRUE; + } else { + // This is very unfortunate, but to handle mutation events properly, + // option list must be up-to-date before inserting or removing options. + // Fortunately this is called only if mutation event listener + // adds or removes options. + select->RebuildOptionsArray(); + } + nsresult rv; + if (aKid) { + rv = mSelect->WillAddOptions(aKid, aParent, aIndex); + } else { + rv = mSelect->WillRemoveOptions(aParent, aIndex); + } + mNeedsRebuild = NS_FAILED(rv); } +} - /** - * Helper method for dispatching ContentReset notifications to list - * and combo box frames. - */ - void DispatchContentReset(); - - /** - * Rebuilds the options array from scratch as a fallback in error cases. - */ - void RebuildOptionsArray(); - +nsSafeOptionListMutation::~nsSafeOptionListMutation() +{ + if (mSelect) { + nsHTMLSelectElement* select = + static_cast(mSelect.get()); + if (mNeedsRebuild || (mTopLevelMutation && mGuard.Mutated(1))) { + select->RebuildOptionsArray(); + } + if (mTopLevelMutation) { + select->mMutating = PR_FALSE; + } #ifdef DEBUG - void VerifyOptionsArray(); + select->VerifyOptionsArray(); #endif - - /** The options[] array */ - nsRefPtr mOptions; - /** false if the parser is in the middle of adding children. */ - PRPackedBool mIsDoneAddingChildren; - /** true if our disabled state has changed from the default **/ - PRPackedBool mDisabledChanged; - /** The number of non-options as children of the select */ - PRUint32 mNonOptionChildren; - /** The number of optgroups anywhere under the select */ - PRUint32 mOptGroupCount; - /** - * The current selected index for selectedIndex (will be the first selected - * index if multiple are selected) - */ - PRInt32 mSelectedIndex; - /** - * The temporary restore state in case we try to restore before parser is - * done adding options - */ - nsRefPtr mRestoreState; -}; - + } +} //---------------------------------------------------------------------- // @@ -502,6 +138,7 @@ nsHTMLSelectElement::nsHTMLSelectElement(nsINodeInfo *aNodeInfo, mOptions(new nsHTMLOptionCollection(this)), mIsDoneAddingChildren(!aFromParser), mDisabledChanged(PR_FALSE), + mMutating(PR_FALSE), mNonOptionChildren(0), mOptGroupCount(0), mSelectedIndex(-1) @@ -561,39 +198,23 @@ nsHTMLSelectElement::InsertChildAt(nsIContent* aKid, PRUint32 aIndex, PRBool aNotify) { - nsresult rv = WillAddOptions(aKid, this, aIndex); - PRBool rebuild = NS_FAILED(rv); - - rv = nsGenericHTMLFormElement::InsertChildAt(aKid, aIndex, aNotify); - if (rebuild || NS_FAILED(rv)) { - RebuildOptionsArray(); - return rv; + nsSafeOptionListMutation safeMutation(this, this, aKid, aIndex); + nsresult rv = nsGenericHTMLFormElement::InsertChildAt(aKid, aIndex, aNotify); + if (NS_FAILED(rv)) { + safeMutation.MutationFailed(); } - -#ifdef DEBUG - VerifyOptionsArray(); -#endif - - return NS_OK; + return rv; } nsresult nsHTMLSelectElement::RemoveChildAt(PRUint32 aIndex, PRBool aNotify) { - nsresult rv = WillRemoveOptions(this, aIndex); - PRBool rebuild = NS_FAILED(rv); - - rv = nsGenericHTMLFormElement::RemoveChildAt(aIndex, aNotify); - if (rebuild || NS_FAILED(rv)) { - RebuildOptionsArray(); - return rv; + nsSafeOptionListMutation safeMutation(this, this, nsnull, aIndex); + nsresult rv = nsGenericHTMLFormElement::RemoveChildAt(aIndex, aNotify); + if (NS_FAILED(rv)) { + safeMutation.MutationFailed(); } - -#ifdef DEBUG - VerifyOptionsArray(); -#endif - - return NS_OK; + return rv; } diff --git a/mozilla/content/html/content/src/nsHTMLSelectElement.h b/mozilla/content/html/content/src/nsHTMLSelectElement.h new file mode 100644 index 00000000000..b7b431007a8 --- /dev/null +++ b/mozilla/content/html/content/src/nsHTMLSelectElement.h @@ -0,0 +1,496 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Pierre Phaneuf + * Mats Palmgren + * Olli Pettay + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#ifndef nsHTMLSelectElement_h___ +#define nsHTMLSelectElement_h___ + +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsGenericHTMLElement.h" +#include "nsISelectElement.h" +#include "nsIDOMHTMLSelectElement.h" +#include "nsIDOMNSHTMLSelectElement.h" +#include "nsIDOMNSXBLFormControl.h" +#include "nsIDOMHTMLFormElement.h" +#include "nsIDOMHTMLOptionElement.h" +#include "nsIDOMHTMLCollection.h" +#include "nsIDOMHTMLOptionsCollection.h" +#include "nsIDOMNSHTMLOptionCollectn.h" +#include "nsISelectControlFrame.h" + +// PresState +#include "nsXPCOM.h" +#include "nsPresState.h" +#include "nsIComponentManager.h" +#include "nsCheapSets.h" +#include "nsLayoutErrors.h" + + +class nsHTMLSelectElement; + +/** + * The collection of options in the select (what you get back when you do + * select.options in DOM) + */ +class nsHTMLOptionCollection: public nsIDOMHTMLOptionsCollection, + public nsIDOMNSHTMLOptionCollection, + public nsIDOMHTMLCollection +{ +public: + nsHTMLOptionCollection(nsHTMLSelectElement* aSelect); + virtual ~nsHTMLOptionCollection(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + + // nsIDOMHTMLOptionsCollection interface + NS_DECL_NSIDOMHTMLOPTIONSCOLLECTION + + // nsIDOMNSHTMLOptionCollection interface + NS_DECL_NSIDOMNSHTMLOPTIONCOLLECTION + + // nsIDOMHTMLCollection interface, all its methods are defined in + // nsIDOMHTMLOptionsCollection + + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHTMLOptionCollection, + nsIDOMNSHTMLOptionCollection) + + // Helpers for nsHTMLSelectElement + /** + * Insert an option + * @param aOption the option to insert + * @param aIndex the index to insert at + */ + PRBool InsertOptionAt(nsIDOMHTMLOptionElement* aOption, PRInt32 aIndex) + { + return mElements.InsertObjectAt(aOption, aIndex); + } + + /** + * Remove an option + * @param aIndex the index of the option to remove + */ + void RemoveOptionAt(PRInt32 aIndex) + { + mElements.RemoveObjectAt(aIndex); + } + + /** + * Get the option at the index + * @param aIndex the index + * @param aReturn the option returned [OUT] + */ + nsIDOMHTMLOptionElement *ItemAsOption(PRInt32 aIndex) + { + return mElements.SafeObjectAt(aIndex); + } + + /** + * Clears out all options + */ + void Clear() + { + mElements.Clear(); + } + + /** + * Append an option to end of array + */ + PRBool AppendOption(nsIDOMHTMLOptionElement* aOption) + { + return mElements.AppendObject(aOption); + } + + /** + * Drop the reference to the select. Called during select destruction. + */ + void DropReference(); + + /** + * See nsISelectElement.idl for documentation on this method + */ + nsresult GetOptionIndex(nsIDOMHTMLOptionElement* aOption, + PRInt32 aStartIndex, PRBool aForward, + PRInt32* aIndex); + +private: + /** The list of options (holds strong references) */ + nsCOMArray mElements; + /** The select element that contains this array */ + nsHTMLSelectElement* mSelect; +}; + + +/** + * The restore state used by select + */ +class nsSelectState : public nsISupports { +public: + nsSelectState() + { + } + virtual ~nsSelectState() + { + } + + NS_DECL_ISUPPORTS + + void PutOption(PRInt32 aIndex, const nsAString& aValue) + { + // If the option is empty, store the index. If not, store the value. + if (aValue.IsEmpty()) { + mIndices.Put(aIndex); + } else { + mValues.Put(aValue); + } + } + + PRBool ContainsOption(PRInt32 aIndex, const nsAString& aValue) + { + return mValues.Contains(aValue) || mIndices.Contains(aIndex); + } + +private: + nsCheapStringSet mValues; + nsCheapInt32Set mIndices; +}; + +class nsSafeOptionListMutation +{ +public: + /** + * @param aSelect The select element which option list is being mutated. + * Can be null. + * @param aParent The content object which is being mutated. + * @param aKid If not null, a new child element is being inserted to + * aParent. Otherwise a child element will be removed. + * @param aIndex The index of the content object in the parent. + */ + nsSafeOptionListMutation(nsIContent* aSelect, nsIContent* aParent, + nsIContent* aKid, PRUint32 aIndex); + ~nsSafeOptionListMutation(); + void MutationFailed() { mNeedsRebuild = PR_TRUE; } +private: + static void* operator new(size_t) CPP_THROW_NEW { return 0; } + static void operator delete(void*, size_t) {} + /** The select element which option list is being mutated. */ + nsCOMPtr mSelect; + /** PR_TRUE if the current mutation is the first one in the stack. */ + PRBool mTopLevelMutation; + /** PR_TRUE if it is known that the option list must be recreated. */ + PRBool mNeedsRebuild; + /** Option list must be recreated if more than one mutation is detected. */ + nsMutationGuard mGuard; +}; + + +/** + * Implementation of <select> + */ +class nsHTMLSelectElement : public nsGenericHTMLFormElement, + public nsIDOMHTMLSelectElement, + public nsIDOMNSHTMLSelectElement, + public nsIDOMNSXBLFormControl, + public nsISelectElement +{ +public: + nsHTMLSelectElement(nsINodeInfo *aNodeInfo, PRBool aFromParser = PR_FALSE); + virtual ~nsHTMLSelectElement(); + + // nsISupports + NS_DECL_ISUPPORTS_INHERITED + + // nsIDOMNode + NS_FORWARD_NSIDOMNODE(nsGenericHTMLFormElement::) + + // nsIDOMElement + NS_FORWARD_NSIDOMELEMENT(nsGenericHTMLFormElement::) + + // nsIDOMHTMLElement + NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLFormElement::) + + // nsIDOMHTMLSelectElement + NS_DECL_NSIDOMHTMLSELECTELEMENT + + // nsIDOMNSHTMLSelectElement + NS_DECL_NSIDOMNSHTMLSELECTELEMENT + + // nsIDOMNSXBLFormControl + NS_DECL_NSIDOMNSXBLFORMCONTROL + + // nsIContent + virtual nsresult PreHandleEvent(nsEventChainPreVisitor& aVisitor); + + virtual void SetFocus(nsPresContext* aPresContext); + virtual PRBool IsFocusable(PRInt32 *aTabIndex = nsnull); + virtual nsresult InsertChildAt(nsIContent* aKid, PRUint32 aIndex, + PRBool aNotify); + virtual nsresult RemoveChildAt(PRUint32 aIndex, PRBool aNotify); + + // Overriden nsIFormControl methods + NS_IMETHOD_(PRInt32) GetType() const { return NS_FORM_SELECT; } + NS_IMETHOD Reset(); + NS_IMETHOD SubmitNamesValues(nsIFormSubmission* aFormSubmission, + nsIContent* aSubmitElement); + NS_IMETHOD SaveState(); + virtual PRBool RestoreState(nsPresState* aState); + + // nsISelectElement + NS_DECL_NSISELECTELEMENT + + /** + * Called when an attribute is about to be changed + */ + virtual nsresult BeforeSetAttr(PRInt32 aNameSpaceID, nsIAtom* aName, + const nsAString* aValue, PRBool aNotify); + virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute, + PRBool aNotify); + + virtual nsresult DoneAddingChildren(PRBool aHaveNotified); + virtual PRBool IsDoneAddingChildren(); + + virtual PRBool ParseAttribute(PRInt32 aNamespaceID, + nsIAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult); + virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const; + virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute, + PRInt32 aModType) const; + NS_IMETHOD_(PRBool) IsAttributeMapped(const nsIAtom* aAttribute) const; + + virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const; + + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLSelectElement, + nsGenericHTMLFormElement) + +protected: + friend class nsSafeOptionListMutation; + + // Helper Methods + /** + * Check whether the option specified by the index is selected + * @param aIndex the index + * @return whether the option at the index is selected + */ + PRBool IsOptionSelectedByIndex(PRInt32 aIndex); + /** + * Starting with (and including) aStartIndex, find the first selected index + * and set mSelectedIndex to it. + * @param aStartIndex the index to start with + */ + void FindSelectedIndex(PRInt32 aStartIndex); + /** + * Select some option if possible (generally the first non-disabled option). + * @return true if something was selected, false otherwise + */ + PRBool SelectSomething(); + /** + * Call SelectSomething(), but only if nothing is selected + * @see SelectSomething() + * @return true if something was selected, false otherwise + */ + PRBool CheckSelectSomething(); + /** + * Called to trigger notifications of frames and fixing selected index + * + * @param aSelectFrame the frame for this content (could be null) + * @param aPresContext the current pres context + * @param aIndex the index that was selected or deselected + * @param aSelected whether the index was selected or deselected + * @param aNotify whether to notify the style system and such + */ + void OnOptionSelected(nsISelectControlFrame* aSelectFrame, + nsPresContext* aPresContext, + PRInt32 aIndex, + PRBool aSelected, + PRBool aNotify); + /** + * Restore state to a particular state string (representing the options) + * @param aNewSelected the state string to restore to + */ + void RestoreStateTo(nsSelectState* aNewSelected); + +#ifdef DEBUG_john + // Don't remove these, por favor. They're very useful in debugging + nsresult PrintOptions(nsIContent* aOptions, PRInt32 tabs); +#endif + + // Adding options + /** + * Insert option(s) into the options[] array and perform notifications + * @param aOptions the option or optgroup being added + * @param aListIndex the index to start adding options into the list at + * @param aDepth the depth of aOptions (1=direct child of select ...) + */ + nsresult InsertOptionsIntoList(nsIContent* aOptions, + PRInt32 aListIndex, + PRInt32 aDepth); + /** + * Remove option(s) from the options[] array + * @param aOptions the option or optgroup being added + * @param aListIndex the index to start removing options from the list at + * @param aDepth the depth of aOptions (1=direct child of select ...) + */ + nsresult RemoveOptionsFromList(nsIContent* aOptions, + PRInt32 aListIndex, + PRInt32 aDepth); + /** + * Insert option(s) into the options[] array (called by InsertOptionsIntoList) + * @param aOptions the option or optgroup being added + * @param aInsertIndex the index to start adding options into the list at + * @param aDepth the depth of aOptions (1=direct child of select ...) + */ + nsresult InsertOptionsIntoListRecurse(nsIContent* aOptions, + PRInt32* aInsertIndex, + PRInt32 aDepth); + /** + * Remove option(s) from the options[] array (called by RemoveOptionsFromList) + * @param aOptions the option or optgroup being added + * @param aListIndex the index to start removing options from the list at + * @param aNumRemoved the number removed so far [OUT] + * @param aDepth the depth of aOptions (1=direct child of select ...) + */ + nsresult RemoveOptionsFromListRecurse(nsIContent* aOptions, + PRInt32 aRemoveIndex, + PRInt32* aNumRemoved, + PRInt32 aDepth); + /** + * Find out how deep this content is from the select (1=direct child) + * @param aContent the content to check + * @return the depth + */ + PRInt32 GetContentDepth(nsIContent* aContent); + /** + * Get the index of the first option at, under or following the content in + * the select, or length of options[] if none are found + * @param aOptions the content + * @return the index of the first option + */ + PRInt32 GetOptionIndexAt(nsIContent* aOptions); + /** + * Get the next option following the content in question (not at or under) + * (this could include siblings of the current content or siblings of the + * parent or children of siblings of the parent). + * @param aOptions the content + * @return the index of the next option after the content + */ + PRInt32 GetOptionIndexAfter(nsIContent* aOptions); + /** + * Get the first option index at or under the content in question. + * @param aOptions the content + * @return the index of the first option at or under the content + */ + PRInt32 GetFirstOptionIndex(nsIContent* aOptions); + /** + * Get the first option index under the content in question, within the + * range specified. + * @param aOptions the content + * @param aStartIndex the first child to look at + * @param aEndIndex the child *after* the last child to look at + * @return the index of the first option at or under the content + */ + PRInt32 GetFirstChildOptionIndex(nsIContent* aOptions, + PRInt32 aStartIndex, + PRInt32 aEndIndex); + + /** + * Get the frame as an nsISelectControlFrame (MAY RETURN NULL) + * @return the select frame, or null + */ + nsISelectControlFrame *GetSelectFrame(); + + /** + * Helper method for dispatching custom DOM events to our anonymous subcontent + * (for XBL form controls) + * @param aName the name of the event to dispatch + */ + void DispatchDOMEvent(const nsAString& aName); + + /** + * Is this a combobox? + */ + PRBool IsCombobox() { + PRBool isMultiple = PR_TRUE; + PRInt32 size = 1; + GetSize(&size); + GetMultiple(&isMultiple); + return !isMultiple && size <= 1; + } + + /** + * Helper method for dispatching ContentReset notifications to list + * and combo box frames. + */ + void DispatchContentReset(); + + /** + * Rebuilds the options array from scratch as a fallback in error cases. + */ + void RebuildOptionsArray(); + +#ifdef DEBUG + void VerifyOptionsArray(); +#endif + + /** The options[] array */ + nsRefPtr mOptions; + /** false if the parser is in the middle of adding children. */ + PRPackedBool mIsDoneAddingChildren; + /** true if our disabled state has changed from the default **/ + PRPackedBool mDisabledChanged; + /** true if child nodes are being added or removed. + * Used by nsSafeOptionListMutation. + */ + PRPackedBool mMutating; + /** The number of non-options as children of the select */ + PRUint32 mNonOptionChildren; + /** The number of optgroups anywhere under the select */ + PRUint32 mOptGroupCount; + /** + * The current selected index for selectedIndex (will be the first selected + * index if multiple are selected) + */ + PRInt32 mSelectedIndex; + /** + * The temporary restore state in case we try to restore before parser is + * done adding options + */ + nsRefPtr mRestoreState; +}; + +#endif