/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/HTMLSelectElement.h" #include "mozAutoDocUpdate.h" #include "mozilla/Attributes.h" #include "mozilla/BasicEvents.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStates.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLOptGroupElement.h" #include "mozilla/dom/HTMLOptionElement.h" #include "mozilla/dom/HTMLSelectElementBinding.h" #include "mozilla/dom/UnionTypes.h" #include "nsContentCreatorFunctions.h" #include "nsContentList.h" #include "nsError.h" #include "nsFormSubmission.h" #include "nsGkAtoms.h" #include "nsIComboboxControlFrame.h" #include "nsIDocument.h" #include "nsIFormControlFrame.h" #include "nsIForm.h" #include "nsIFormProcessor.h" #include "nsIFrame.h" #include "nsIListControlFrame.h" #include "nsISelectControlFrame.h" #include "nsLayoutUtils.h" #include "nsMappedAttributes.h" #include "nsPresState.h" #include "nsRuleData.h" #include "nsServiceManagerUtils.h" #include "nsStyleConsts.h" #include "nsTextNode.h" NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Select) namespace mozilla { namespace dom { NS_IMPL_ISUPPORTS(SelectState, SelectState) //---------------------------------------------------------------------- // // SafeOptionListMutation // SafeOptionListMutation::SafeOptionListMutation(nsIContent* aSelect, nsIContent* aParent, nsIContent* aKid, uint32_t aIndex, bool aNotify) : mSelect(HTMLSelectElement::FromContentOrNull(aSelect)) , mTopLevelMutation(false) , mNeedsRebuild(false) { if (mSelect) { mTopLevelMutation = !mSelect->mMutating; if (mTopLevelMutation) { mSelect->mMutating = 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. mSelect->RebuildOptionsArray(aNotify); } nsresult rv; if (aKid) { rv = mSelect->WillAddOptions(aKid, aParent, aIndex, aNotify); } else { rv = mSelect->WillRemoveOptions(aParent, aIndex, aNotify); } mNeedsRebuild = NS_FAILED(rv); } } SafeOptionListMutation::~SafeOptionListMutation() { if (mSelect) { if (mNeedsRebuild || (mTopLevelMutation && mGuard.Mutated(1))) { mSelect->RebuildOptionsArray(true); } if (mTopLevelMutation) { mSelect->mMutating = false; } #ifdef DEBUG mSelect->VerifyOptionsArray(); #endif } } //---------------------------------------------------------------------- // // HTMLSelectElement // // construction, destruction HTMLSelectElement::HTMLSelectElement(already_AddRefed& aNodeInfo, FromParser aFromParser) : nsGenericHTMLFormElementWithState(aNodeInfo), mOptions(new HTMLOptionsCollection(this)), mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown), mIsDoneAddingChildren(!aFromParser), mDisabledChanged(false), mMutating(false), mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)), mSelectionHasChanged(false), mDefaultSelectionSet(false), mCanShowInvalidUI(true), mCanShowValidUI(true), mNonOptionChildren(0), mOptGroupCount(0), mSelectedIndex(-1) { SetHasWeirdParserInsertionMode(); // DoneAddingChildren() will be called later if it's from the parser, // otherwise it is // Set up our default state: enabled, optional, and valid. AddStatesSilently(NS_EVENT_STATE_ENABLED | NS_EVENT_STATE_OPTIONAL | NS_EVENT_STATE_VALID); } HTMLSelectElement::~HTMLSelectElement() { mOptions->DropReference(); } // ISupports NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLSelectElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLSelectElement, nsGenericHTMLFormElementWithState) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOptions) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedOptions) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLSelectElement, nsGenericHTMLFormElementWithState) NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedOptions) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_ADDREF_INHERITED(HTMLSelectElement, Element) NS_IMPL_RELEASE_INHERITED(HTMLSelectElement, Element) // QueryInterface implementation for HTMLSelectElement NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLSelectElement) NS_INTERFACE_TABLE_INHERITED(HTMLSelectElement, nsIDOMHTMLSelectElement, nsIConstraintValidation) NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState) // nsIDOMHTMLSelectElement NS_IMPL_ELEMENT_CLONE(HTMLSelectElement) // nsIConstraintValidation NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLSelectElement) NS_IMETHODIMP HTMLSelectElement::SetCustomValidity(const nsAString& aError) { nsIConstraintValidation::SetCustomValidity(aError); UpdateState(true); return NS_OK; } void HTMLSelectElement::GetAutocomplete(DOMString& aValue) { const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete); mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue, mAutocompleteAttrState); } NS_IMETHODIMP HTMLSelectElement::GetForm(nsIDOMHTMLFormElement** aForm) { return nsGenericHTMLFormElementWithState::GetForm(aForm); } nsresult HTMLSelectElement::InsertChildAt(nsIContent* aKid, uint32_t aIndex, bool aNotify) { SafeOptionListMutation safeMutation(this, this, aKid, aIndex, aNotify); nsresult rv = nsGenericHTMLFormElementWithState::InsertChildAt(aKid, aIndex, aNotify); if (NS_FAILED(rv)) { safeMutation.MutationFailed(); } return rv; } void HTMLSelectElement::RemoveChildAt(uint32_t aIndex, bool aNotify) { SafeOptionListMutation safeMutation(this, this, nullptr, aIndex, aNotify); nsGenericHTMLFormElementWithState::RemoveChildAt(aIndex, aNotify); } void HTMLSelectElement::InsertOptionsIntoList(nsIContent* aOptions, int32_t aListIndex, int32_t aDepth, bool aNotify) { int32_t insertIndex = aListIndex; InsertOptionsIntoListRecurse(aOptions, &insertIndex, aDepth); // Deal with the selected list if (insertIndex - aListIndex) { // Fix the currently selected index if (aListIndex <= mSelectedIndex) { mSelectedIndex += (insertIndex - aListIndex); SetSelectionChanged(true, aNotify); } // Get the frame stuff for notification. No need to flush here // since if there's no frame for the select yet the select will // get into the right state once it's created. nsISelectControlFrame* selectFrame = nullptr; nsWeakFrame weakSelectFrame; bool didGetFrame = false; // Actually select the options if the added options warrant it for (int32_t i = aListIndex; i < insertIndex; i++) { // Notify the frame that the option is added if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) { selectFrame = GetSelectFrame(); weakSelectFrame = do_QueryFrame(selectFrame); didGetFrame = true; } if (selectFrame) { selectFrame->AddOption(i); } nsRefPtr option = Item(i); if (option && option->Selected()) { // Clear all other options if (!HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) { uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED | NOTIFY; SetOptionsSelectedByIndex(i, i, mask); } // This is sort of a hack ... we need to notify that the option was // set and change selectedIndex even though we didn't really change // its value. OnOptionSelected(selectFrame, i, true, false, false); } } CheckSelectSomething(aNotify); } } nsresult HTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions, int32_t aListIndex, int32_t aDepth, bool aNotify) { int32_t numRemoved = 0; nsresult rv = RemoveOptionsFromListRecurse(aOptions, aListIndex, &numRemoved, aDepth); NS_ENSURE_SUCCESS(rv, rv); if (numRemoved) { // Tell the widget we removed the options nsISelectControlFrame* selectFrame = GetSelectFrame(); if (selectFrame) { nsAutoScriptBlocker scriptBlocker; for (int32_t i = aListIndex; i < aListIndex + numRemoved; ++i) { selectFrame->RemoveOption(i); } } // Fix the selected index if (aListIndex <= mSelectedIndex) { if (mSelectedIndex < (aListIndex+numRemoved)) { // aListIndex <= mSelectedIndex < aListIndex+numRemoved // Find a new selected index if it was one of the ones removed. FindSelectedIndex(aListIndex, aNotify); } else { // Shift the selected index if something in front of it was removed // aListIndex+numRemoved <= mSelectedIndex mSelectedIndex -= numRemoved; SetSelectionChanged(true, aNotify); } } // Select something in case we removed the selected option on a // single select if (!CheckSelectSomething(aNotify) && mSelectedIndex == -1) { // Update the validity state in case of we've just removed the last // option. UpdateValueMissingValidityState(); UpdateState(aNotify); } } return NS_OK; } // If the document is such that recursing over these options gets us // deeper than four levels, there is something terribly wrong with the // world. void HTMLSelectElement::InsertOptionsIntoListRecurse(nsIContent* aOptions, int32_t* aInsertIndex, int32_t aDepth) { // We *assume* here that someone's brain has not gone horribly // wrong by putting