diff options
Diffstat (limited to 'dom/html/HTMLSelectElement.cpp')
-rw-r--r-- | dom/html/HTMLSelectElement.cpp | 1917 |
1 files changed, 1917 insertions, 0 deletions
diff --git a/dom/html/HTMLSelectElement.cpp b/dom/html/HTMLSelectElement.cpp new file mode 100644 index 000000000..24ddabb65 --- /dev/null +++ b/dom/html/HTMLSelectElement.cpp @@ -0,0 +1,1917 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/HTMLFormSubmission.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 "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<mozilla::dom::NodeInfo>& 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) +{ + MOZ_ASSERT(aDepth == 0 || aDepth == 1); + int32_t insertIndex = aListIndex; + + HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions); + if (optElement) { + mOptions->InsertOptionAt(optElement, insertIndex); + insertIndex++; + } else if (aDepth == 0) { + // If it's at the top level, then we just found out there are non-options + // at the top level, which will throw off the insert count + mNonOptionChildren++; + + // Deal with optgroups + if (aOptions->IsHTMLElement(nsGkAtoms::optgroup)) { + mOptGroupCount++; + + for (nsIContent* child = aOptions->GetFirstChild(); + child; + child = child->GetNextSibling()) { + optElement = HTMLOptionElement::FromContent(child); + if (optElement) { + mOptions->InsertOptionAt(optElement, insertIndex); + insertIndex++; + } + } + } + } // else ignore even if optgroup; we want to ignore nested optgroups. + + // 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); + } + + RefPtr<HTMLOptionElement> 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) +{ + MOZ_ASSERT(aDepth == 0 || aDepth == 1); + int32_t numRemoved = 0; + + HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions); + if (optElement) { + if (mOptions->ItemAsOption(aListIndex) != optElement) { + NS_ERROR("wrong option at index"); + return NS_ERROR_UNEXPECTED; + } + mOptions->RemoveOptionAt(aListIndex); + numRemoved++; + } else if (aDepth == 0) { + // Yay, one less artifact at the top level. + mNonOptionChildren--; + + // Recurse down deeper for options + if (mOptGroupCount && aOptions->IsHTMLElement(nsGkAtoms::optgroup)) { + mOptGroupCount--; + + for (nsIContent* child = aOptions->GetFirstChild(); + child; + child = child->GetNextSibling()) { + optElement = HTMLOptionElement::FromContent(child); + if (optElement) { + if (mOptions->ItemAsOption(aListIndex) != optElement) { + NS_ERROR("wrong option at index"); + return NS_ERROR_UNEXPECTED; + } + mOptions->RemoveOptionAt(aListIndex); + numRemoved++; + } + } + } + } // else don't check for an optgroup; we want to ignore nested optgroups + + 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; +} + +// XXXldb Doing the processing before the content nodes have been added +// to the document (as the name of this function seems to require, and +// as the callers do), is highly unusual. Passing around unparented +// content to other parts of the app can make those things think the +// options are the root content node. +NS_IMETHODIMP +HTMLSelectElement::WillAddOptions(nsIContent* aOptions, + nsIContent* aParent, + int32_t aContentIndex, + bool aNotify) +{ + if (this != aParent && this != aParent->GetParent()) { + return NS_OK; + } + int32_t level = aParent == this ? 0 : 1; + + // Get the index where the options will be inserted + int32_t ind = -1; + if (!mNonOptionChildren) { + // If there are no artifacts, aContentIndex == ind + ind = aContentIndex; + } else { + // If there are artifacts, we have to get the index of the option the + // hard way + int32_t children = aParent->GetChildCount(); + + if (aContentIndex >= children) { + // If the content insert is after the end of the parent, then we want to get + // the next index *after* the parent and insert there. + ind = GetOptionIndexAfter(aParent); + } else { + // If the content insert is somewhere in the middle of the container, then + // we want to get the option currently at the index and insert in front of + // that. + nsIContent* currentKid = aParent->GetChildAt(aContentIndex); + NS_ASSERTION(currentKid, "Child not found!"); + if (currentKid) { + ind = GetOptionIndexAt(currentKid); + } else { + ind = -1; + } + } + } + + InsertOptionsIntoList(aOptions, ind, level, aNotify); + return NS_OK; +} + +NS_IMETHODIMP +HTMLSelectElement::WillRemoveOptions(nsIContent* aParent, + int32_t aContentIndex, + bool aNotify) +{ + if (this != aParent && this != aParent->GetParent()) { + return NS_OK; + } + int32_t level = this == aParent ? 0 : 1; + + // Get the index where the options will be removed + nsIContent* currentKid = aParent->GetChildAt(aContentIndex); + if (currentKid) { + int32_t ind; + if (!mNonOptionChildren) { + // If there are no artifacts, aContentIndex == ind + ind = aContentIndex; + } else { + // If there are artifacts, we have to get the index of the option the + // hard way + ind = GetFirstOptionIndex(currentKid); + } + if (ind != -1) { + nsresult rv = RemoveOptionsFromList(currentKid, ind, level, aNotify); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +int32_t +HTMLSelectElement::GetOptionIndexAt(nsIContent* aOptions) +{ + // Search this node and below. + // If not found, find the first one *after* this node. + int32_t retval = GetFirstOptionIndex(aOptions); + if (retval == -1) { + retval = GetOptionIndexAfter(aOptions); + } + + return retval; +} + +int32_t +HTMLSelectElement::GetOptionIndexAfter(nsIContent* aOptions) +{ + // - If this is the select, the next option is the last. + // - If not, search all the options after aOptions and up to the last option + // in the parent. + // - If it's not there, search for the first option after the parent. + if (aOptions == this) { + return Length(); + } + + int32_t retval = -1; + + nsCOMPtr<nsIContent> parent = aOptions->GetParent(); + + if (parent) { + int32_t index = parent->IndexOf(aOptions); + int32_t count = parent->GetChildCount(); + + retval = GetFirstChildOptionIndex(parent, index+1, count); + + if (retval == -1) { + retval = GetOptionIndexAfter(parent); + } + } + + return retval; +} + +int32_t +HTMLSelectElement::GetFirstOptionIndex(nsIContent* aOptions) +{ + int32_t listIndex = -1; + HTMLOptionElement* optElement = HTMLOptionElement::FromContent(aOptions); + if (optElement) { + GetOptionIndex(optElement, 0, true, &listIndex); + return listIndex; + } + + listIndex = GetFirstChildOptionIndex(aOptions, 0, aOptions->GetChildCount()); + + return listIndex; +} + +int32_t +HTMLSelectElement::GetFirstChildOptionIndex(nsIContent* aOptions, + int32_t aStartIndex, + int32_t aEndIndex) +{ + int32_t retval = -1; + + for (int32_t i = aStartIndex; i < aEndIndex; ++i) { + retval = GetFirstOptionIndex(aOptions->GetChildAt(i)); + if (retval != -1) { + break; + } + } + + return retval; +} + +nsISelectControlFrame* +HTMLSelectElement::GetSelectFrame() +{ + nsIFormControlFrame* form_control_frame = GetFormControlFrame(false); + + nsISelectControlFrame* select_frame = nullptr; + + if (form_control_frame) { + select_frame = do_QueryFrame(form_control_frame); + } + + return select_frame; +} + +void +HTMLSelectElement::Add(const HTMLOptionElementOrHTMLOptGroupElement& aElement, + const Nullable<HTMLElementOrLong>& aBefore, + ErrorResult& aRv) +{ + nsGenericHTMLElement& element = + aElement.IsHTMLOptionElement() ? + static_cast<nsGenericHTMLElement&>(aElement.GetAsHTMLOptionElement()) : + static_cast<nsGenericHTMLElement&>(aElement.GetAsHTMLOptGroupElement()); + + if (aBefore.IsNull()) { + Add(element, static_cast<nsGenericHTMLElement*>(nullptr), aRv); + } else if (aBefore.Value().IsHTMLElement()) { + Add(element, &aBefore.Value().GetAsHTMLElement(), aRv); + } else { + Add(element, aBefore.Value().GetAsLong(), aRv); + } +} + +void +HTMLSelectElement::Add(nsGenericHTMLElement& aElement, + nsGenericHTMLElement* aBefore, + ErrorResult& aError) +{ + if (!aBefore) { + Element::AppendChild(aElement, aError); + return; + } + + // Just in case we're not the parent, get the parent of the reference + // element + nsCOMPtr<nsINode> parent = aBefore->Element::GetParentNode(); + if (!parent || !nsContentUtils::ContentIsDescendantOf(parent, this)) { + // NOT_FOUND_ERR: Raised if before is not a descendant of the SELECT + // element. + aError.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); + return; + } + + // If the before parameter is not null, we are equivalent to the + // insertBefore method on the parent of before. + nsCOMPtr<nsINode> refNode = aBefore; + parent->InsertBefore(aElement, refNode, aError); +} + +NS_IMETHODIMP +HTMLSelectElement::Add(nsIDOMHTMLElement* aElement, + nsIVariant* aBefore) +{ + uint16_t dataType; + nsresult rv = aBefore->GetDataType(&dataType); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContent> element = do_QueryInterface(aElement); + nsGenericHTMLElement* htmlElement = + nsGenericHTMLElement::FromContentOrNull(element); + if (!htmlElement) { + return NS_ERROR_NULL_POINTER; + } + + // aBefore is omitted, undefined or null + if (dataType == nsIDataType::VTYPE_EMPTY || + dataType == nsIDataType::VTYPE_VOID) { + ErrorResult error; + Add(*htmlElement, (nsGenericHTMLElement*)nullptr, error); + return error.StealNSResult(); + } + + nsCOMPtr<nsISupports> supports; + + // whether aBefore is nsIDOMHTMLElement... + if (NS_SUCCEEDED(aBefore->GetAsISupports(getter_AddRefs(supports)))) { + nsCOMPtr<nsIContent> beforeElement = do_QueryInterface(supports); + nsGenericHTMLElement* beforeHTMLElement = + nsGenericHTMLElement::FromContentOrNull(beforeElement); + + NS_ENSURE_TRUE(beforeHTMLElement, NS_ERROR_DOM_SYNTAX_ERR); + + ErrorResult error; + Add(*htmlElement, beforeHTMLElement, error); + return error.StealNSResult(); + } + + // otherwise, whether aBefore is long + int32_t index; + NS_ENSURE_SUCCESS(aBefore->GetAsInt32(&index), NS_ERROR_DOM_SYNTAX_ERR); + + ErrorResult error; + Add(*htmlElement, index, error); + return error.StealNSResult(); +} + +NS_IMETHODIMP +HTMLSelectElement::Remove(int32_t aIndex) +{ + nsCOMPtr<nsINode> option = Item(static_cast<uint32_t>(aIndex)); + if (!option) { + return NS_OK; + } + + option->Remove(); + return NS_OK; +} + +NS_IMETHODIMP +HTMLSelectElement::GetOptions(nsIDOMHTMLOptionsCollection** aValue) +{ + NS_IF_ADDREF(*aValue = GetOptions()); + + return NS_OK; +} + +NS_IMETHODIMP +HTMLSelectElement::GetType(nsAString& aType) +{ + if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) { + aType.AssignLiteral("select-multiple"); + } + else { + aType.AssignLiteral("select-one"); + } + + return NS_OK; +} + +NS_IMETHODIMP +HTMLSelectElement::GetLength(uint32_t* aLength) +{ + return mOptions->GetLength(aLength); +} + +#define MAX_DYNAMIC_SELECT_LENGTH 10000 + +NS_IMETHODIMP +HTMLSelectElement::SetLength(uint32_t aLength) +{ + ErrorResult rv; + SetLength(aLength, rv); + return rv.StealNSResult(); +} + +void +HTMLSelectElement::SetLength(uint32_t aLength, ErrorResult& aRv) +{ + uint32_t curlen = Length(); + + if (curlen > aLength) { // Remove extra options + for (uint32_t i = curlen; i > aLength; --i) { + MOZ_ALWAYS_SUCCEEDS(Remove(i - 1)); + } + } else if (aLength > curlen) { + if (aLength > MAX_DYNAMIC_SELECT_LENGTH) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return; + } + + RefPtr<mozilla::dom::NodeInfo> nodeInfo; + + nsContentUtils::NameChanged(mNodeInfo, nsGkAtoms::option, + getter_AddRefs(nodeInfo)); + + nsCOMPtr<nsINode> node = NS_NewHTMLOptionElement(nodeInfo.forget()); + + RefPtr<nsTextNode> text = new nsTextNode(mNodeInfo->NodeInfoManager()); + + aRv = node->AppendChildTo(text, false); + if (aRv.Failed()) { + return; + } + + for (uint32_t i = curlen; i < aLength; i++) { + nsINode::AppendChild(*node, aRv); + if (aRv.Failed()) { + return; + } + + if (i + 1 < aLength) { + node = node->CloneNode(true, aRv); + if (aRv.Failed()) { + return; + } + MOZ_ASSERT(node); + } + } + } +} + +/* static */ +bool +HTMLSelectElement::MatchSelectedOptions(nsIContent* aContent, + int32_t /* unused */, + nsIAtom* /* unused */, + void* /* unused*/) +{ + HTMLOptionElement* option = HTMLOptionElement::FromContent(aContent); + return option && option->Selected(); +} + +nsIHTMLCollection* +HTMLSelectElement::SelectedOptions() +{ + if (!mSelectedOptions) { + mSelectedOptions = new nsContentList(this, MatchSelectedOptions, nullptr, + nullptr, /* deep */ true); + } + return mSelectedOptions; +} + +NS_IMETHODIMP +HTMLSelectElement::GetSelectedOptions(nsIDOMHTMLCollection** aSelectedOptions) +{ + NS_ADDREF(*aSelectedOptions = SelectedOptions()); + return NS_OK; +} + +//NS_IMPL_INT_ATTR(HTMLSelectElement, SelectedIndex, selectedindex) + +NS_IMETHODIMP +HTMLSelectElement::GetSelectedIndex(int32_t* aValue) +{ + *aValue = SelectedIndex(); + + return NS_OK; +} + +nsresult +HTMLSelectElement::SetSelectedIndexInternal(int32_t aIndex, bool aNotify) +{ + int32_t oldSelectedIndex = mSelectedIndex; + uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED; + if (aNotify) { + mask |= NOTIFY; + } + + SetOptionsSelectedByIndex(aIndex, aIndex, mask); + + nsresult rv = NS_OK; + nsISelectControlFrame* selectFrame = GetSelectFrame(); + if (selectFrame) { + rv = selectFrame->OnSetSelectedIndex(oldSelectedIndex, mSelectedIndex); + } + + SetSelectionChanged(true, aNotify); + + return rv; +} + +NS_IMETHODIMP +HTMLSelectElement::SetSelectedIndex(int32_t aIndex) +{ + return SetSelectedIndexInternal(aIndex, true); +} + +NS_IMETHODIMP +HTMLSelectElement::GetOptionIndex(nsIDOMHTMLOptionElement* aOption, + int32_t aStartIndex, bool aForward, + int32_t* aIndex) +{ + nsCOMPtr<nsINode> option = do_QueryInterface(aOption); + return mOptions->GetOptionIndex(option->AsElement(), aStartIndex, aForward, aIndex); +} + +bool +HTMLSelectElement::IsOptionSelectedByIndex(int32_t aIndex) +{ + HTMLOptionElement* option = Item(static_cast<uint32_t>(aIndex)); + return option && option->Selected(); +} + +void +HTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame, + int32_t aIndex, + bool aSelected, + bool aChangeOptionState, + bool aNotify) +{ + // Set the selected index + if (aSelected && (aIndex < mSelectedIndex || mSelectedIndex < 0)) { + mSelectedIndex = aIndex; + SetSelectionChanged(true, aNotify); + } else if (!aSelected && aIndex == mSelectedIndex) { + FindSelectedIndex(aIndex + 1, aNotify); + } + + if (aChangeOptionState) { + // Tell the option to get its bad self selected + RefPtr<HTMLOptionElement> option = Item(static_cast<uint32_t>(aIndex)); + if (option) { + option->SetSelectedInternal(aSelected, aNotify); + } + } + + // Let the frame know too + if (aSelectFrame) { + aSelectFrame->OnOptionSelected(aIndex, aSelected); + } + + UpdateSelectedOptions(); + UpdateValueMissingValidityState(); + UpdateState(aNotify); +} + +void +HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex, bool aNotify) +{ + mSelectedIndex = -1; + SetSelectionChanged(true, aNotify); + uint32_t len = Length(); + for (int32_t i = aStartIndex; i < int32_t(len); i++) { + if (IsOptionSelectedByIndex(i)) { + mSelectedIndex = i; + SetSelectionChanged(true, aNotify); + break; + } + } +} + +// XXX Consider splitting this into two functions for ease of reading: +// SelectOptionsByIndex(startIndex, endIndex, clearAll, checkDisabled) +// startIndex, endIndex - the range of options to turn on +// (-1, -1) will clear all indices no matter what. +// clearAll - will clear all other options unless checkDisabled is on +// and all the options attempted to be set are disabled +// (note that if it is not multiple, and an option is selected, +// everything else will be cleared regardless). +// checkDisabled - if this is TRUE, and an option is disabled, it will not be +// changed regardless of whether it is selected or not. +// Generally the UI passes TRUE and JS passes FALSE. +// (setDisabled currently is the opposite) +// DeselectOptionsByIndex(startIndex, endIndex, checkDisabled) +// startIndex, endIndex - the range of options to turn on +// (-1, -1) will clear all indices no matter what. +// checkDisabled - if this is TRUE, and an option is disabled, it will not be +// changed regardless of whether it is selected or not. +// Generally the UI passes TRUE and JS passes FALSE. +// (setDisabled currently is the opposite) +// +// XXXbz the above comment is pretty confusing. Maybe we should actually +// document the args to this function too, in addition to documenting what +// things might end up looking like? In particular, pay attention to the +// setDisabled vs checkDisabled business. +bool +HTMLSelectElement::SetOptionsSelectedByIndex(int32_t aStartIndex, + int32_t aEndIndex, + uint32_t aOptionsMask) +{ +#if 0 + printf("SetOption(%d-%d, %c, ClearAll=%c)\n", aStartIndex, aEndIndex, + (aOptionsMask & IS_SELECTED ? 'Y' : 'N'), + (aOptionsMask & CLEAR_ALL ? 'Y' : 'N')); +#endif + // Don't bother if the select is disabled + if (!(aOptionsMask & SET_DISABLED) && IsDisabled()) { + return false; + } + + // Don't bother if there are no options + uint32_t numItems = Length(); + if (numItems == 0) { + return false; + } + + // First, find out whether multiple items can be selected + bool isMultiple = Multiple(); + + // These variables tell us whether any options were selected + // or deselected. + bool optionsSelected = false; + bool optionsDeselected = false; + + nsISelectControlFrame* selectFrame = nullptr; + bool didGetFrame = false; + nsWeakFrame weakSelectFrame; + + if (aOptionsMask & IS_SELECTED) { + // Setting selectedIndex to an out-of-bounds index means -1. (HTML5) + if (aStartIndex < 0 || AssertedCast<uint32_t>(aStartIndex) >= numItems || + aEndIndex < 0 || AssertedCast<uint32_t>(aEndIndex) >= numItems) { + aStartIndex = -1; + aEndIndex = -1; + } + + // Only select the first value if it's not multiple + if (!isMultiple) { + aEndIndex = aStartIndex; + } + + // This variable tells whether or not all of the options we attempted to + // select are disabled. If ClearAll is passed in as true, and we do not + // select anything because the options are disabled, we will not clear the + // other options. (This is to make the UI work the way one might expect.) + bool allDisabled = !(aOptionsMask & SET_DISABLED); + + // + // Save a little time when clearing other options + // + int32_t previousSelectedIndex = mSelectedIndex; + + // + // Select the requested indices + // + // If index is -1, everything will be deselected (bug 28143) + if (aStartIndex != -1) { + MOZ_ASSERT(aStartIndex >= 0); + MOZ_ASSERT(aEndIndex >= 0); + // Loop through the options and select them (if they are not disabled and + // if they are not already selected). + for (uint32_t optIndex = AssertedCast<uint32_t>(aStartIndex); + optIndex <= AssertedCast<uint32_t>(aEndIndex); + optIndex++) { + RefPtr<HTMLOptionElement> option = Item(optIndex); + + // Ignore disabled options. + if (!(aOptionsMask & SET_DISABLED)) { + if (option && IsOptionDisabled(option)) { + continue; + } + allDisabled = false; + } + + // If the index is already selected, ignore it. + if (option && !option->Selected()) { + // To notify the frame if anything gets changed. No need + // to flush here, if there's no frame yet we don't need to + // force it to be created just to notify it about a change + // in the select. + selectFrame = GetSelectFrame(); + weakSelectFrame = do_QueryFrame(selectFrame); + didGetFrame = true; + + OnOptionSelected(selectFrame, optIndex, true, true, + aOptionsMask & NOTIFY); + optionsSelected = true; + } + } + } + + // Next remove all other options if single select or all is clear + // If index is -1, everything will be deselected (bug 28143) + if (((!isMultiple && optionsSelected) + || ((aOptionsMask & CLEAR_ALL) && !allDisabled) + || aStartIndex == -1) + && previousSelectedIndex != -1) { + for (uint32_t optIndex = AssertedCast<uint32_t>(previousSelectedIndex); + optIndex < numItems; + optIndex++) { + if (static_cast<int32_t>(optIndex) < aStartIndex || + static_cast<int32_t>(optIndex) > aEndIndex) { + HTMLOptionElement* option = Item(optIndex); + // If the index is already selected, ignore it. + if (option && option->Selected()) { + if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) { + // To notify the frame if anything gets changed, don't + // flush, if the frame doesn't exist we don't need to + // create it just to tell it about this change. + selectFrame = GetSelectFrame(); + weakSelectFrame = do_QueryFrame(selectFrame); + + didGetFrame = true; + } + + OnOptionSelected(selectFrame, optIndex, false, true, + aOptionsMask & NOTIFY); + optionsDeselected = true; + + // Only need to deselect one option if not multiple + if (!isMultiple) { + break; + } + } + } + } + } + } else { + // If we're deselecting, loop through all selected items and deselect + // any that are in the specified range. + for (int32_t optIndex = aStartIndex; optIndex <= aEndIndex; optIndex++) { + HTMLOptionElement* option = Item(optIndex); + if (!(aOptionsMask & SET_DISABLED) && IsOptionDisabled(option)) { + continue; + } + + // If the index is already selected, ignore it. + if (option && option->Selected()) { + if (!didGetFrame || (selectFrame && !weakSelectFrame.IsAlive())) { + // To notify the frame if anything gets changed, don't + // flush, if the frame doesn't exist we don't need to + // create it just to tell it about this change. + selectFrame = GetSelectFrame(); + weakSelectFrame = do_QueryFrame(selectFrame); + + didGetFrame = true; + } + + OnOptionSelected(selectFrame, optIndex, false, true, + aOptionsMask & NOTIFY); + optionsDeselected = true; + } + } + } + + // Make sure something is selected unless we were set to -1 (none) + if (optionsDeselected && aStartIndex != -1) { + optionsSelected = + CheckSelectSomething(aOptionsMask & NOTIFY) || optionsSelected; + } + + // Let the caller know whether anything was changed + return optionsSelected || optionsDeselected; +} + +NS_IMETHODIMP +HTMLSelectElement::IsOptionDisabled(int32_t aIndex, bool* aIsDisabled) +{ + *aIsDisabled = false; + RefPtr<HTMLOptionElement> option = Item(aIndex); + NS_ENSURE_TRUE(option, NS_ERROR_FAILURE); + + *aIsDisabled = IsOptionDisabled(option); + return NS_OK; +} + +bool +HTMLSelectElement::IsOptionDisabled(HTMLOptionElement* aOption) +{ + MOZ_ASSERT(aOption); + if (aOption->Disabled()) { + return true; + } + + // Check for disabled optgroups + // If there are no artifacts, there are no optgroups + if (mNonOptionChildren) { + for (nsCOMPtr<Element> node = static_cast<nsINode*>(aOption)->GetParentElement(); + node; + node = node->GetParentElement()) { + // If we reached the select element, we're done + if (node->IsHTMLElement(nsGkAtoms::select)) { + return false; + } + + RefPtr<HTMLOptGroupElement> optGroupElement = + HTMLOptGroupElement::FromContent(node); + + if (!optGroupElement) { + // If you put something else between you and the optgroup, you're a + // moron and you deserve not to have optgroup disabling work. + return false; + } + + if (optGroupElement->Disabled()) { + return true; + } + } + } + + return false; +} + +NS_IMETHODIMP +HTMLSelectElement::GetValue(nsAString& aValue) +{ + DOMString value; + GetValue(value); + value.ToString(aValue); + return NS_OK; +} + +void +HTMLSelectElement::GetValue(DOMString& aValue) +{ + int32_t selectedIndex = SelectedIndex(); + if (selectedIndex < 0) { + return; + } + + RefPtr<HTMLOptionElement> option = + Item(static_cast<uint32_t>(selectedIndex)); + + if (!option) { + return; + } + + DebugOnly<nsresult> rv = option->GetValue(aValue); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +} + +NS_IMETHODIMP +HTMLSelectElement::SetValue(const nsAString& aValue) +{ + uint32_t length = Length(); + + for (uint32_t i = 0; i < length; i++) { + RefPtr<HTMLOptionElement> option = Item(i); + if (!option) { + continue; + } + + nsAutoString optionVal; + option->GetValue(optionVal); + if (optionVal.Equals(aValue)) { + SetSelectedIndexInternal(int32_t(i), true); + return NS_OK; + } + } + // No matching option was found. + SetSelectedIndexInternal(-1, true); + return NS_OK; +} + + +NS_IMPL_BOOL_ATTR(HTMLSelectElement, Autofocus, autofocus) +NS_IMPL_BOOL_ATTR(HTMLSelectElement, Disabled, disabled) +NS_IMPL_BOOL_ATTR(HTMLSelectElement, Multiple, multiple) +NS_IMPL_STRING_ATTR(HTMLSelectElement, Name, name) +NS_IMPL_BOOL_ATTR(HTMLSelectElement, Required, required) +NS_IMPL_UINT_ATTR(HTMLSelectElement, Size, size) + +int32_t +HTMLSelectElement::TabIndexDefault() +{ + return 0; +} + +bool +HTMLSelectElement::IsHTMLFocusable(bool aWithMouse, + bool* aIsFocusable, int32_t* aTabIndex) +{ + if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable, + aTabIndex)) + { + return true; + } + + *aIsFocusable = !IsDisabled(); + + return false; +} + +NS_IMETHODIMP +HTMLSelectElement::Item(uint32_t aIndex, nsIDOMNode** aReturn) +{ + return mOptions->Item(aIndex, aReturn); +} + +NS_IMETHODIMP +HTMLSelectElement::NamedItem(const nsAString& aName, nsIDOMNode** aReturn) +{ + return mOptions->NamedItem(aName, aReturn); +} + +bool +HTMLSelectElement::CheckSelectSomething(bool aNotify) +{ + if (mIsDoneAddingChildren) { + if (mSelectedIndex < 0 && IsCombobox()) { + return SelectSomething(aNotify); + } + } + return false; +} + +bool +HTMLSelectElement::SelectSomething(bool aNotify) +{ + // If we're not done building the select, don't play with this yet. + if (!mIsDoneAddingChildren) { + return false; + } + + uint32_t count; + GetLength(&count); + for (uint32_t i = 0; i < count; i++) { + bool disabled; + nsresult rv = IsOptionDisabled(i, &disabled); + + if (NS_FAILED(rv) || !disabled) { + rv = SetSelectedIndexInternal(i, aNotify); + NS_ENSURE_SUCCESS(rv, false); + + UpdateValueMissingValidityState(); + UpdateState(aNotify); + + return true; + } + } + + return false; +} + +nsresult +HTMLSelectElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, + nsIContent* aBindingParent, + bool aCompileEventHandlers) +{ + nsresult rv = nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent, + aBindingParent, + aCompileEventHandlers); + NS_ENSURE_SUCCESS(rv, rv); + + // If there is a disabled fieldset in the parent chain, the element is now + // barred from constraint validation. + // XXXbz is this still needed now that fieldset changes always call + // FieldSetDisabledChanged? + UpdateBarredFromConstraintValidation(); + + // And now make sure our state is up to date + UpdateState(false); + + return rv; +} + +void +HTMLSelectElement::UnbindFromTree(bool aDeep, bool aNullParent) +{ + nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent); + + // We might be no longer disabled because our parent chain changed. + // XXXbz is this still needed now that fieldset changes always call + // FieldSetDisabledChanged? + UpdateBarredFromConstraintValidation(); + + // And now make sure our state is up to date + UpdateState(false); +} + +nsresult +HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName, + nsAttrValueOrString* aValue, + bool aNotify) +{ + if (aNotify && aName == nsGkAtoms::disabled && + aNameSpaceID == kNameSpaceID_None) { + mDisabledChanged = true; + } + + return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName, + aValue, aNotify); +} + +nsresult +HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, + const nsAttrValue* aValue, bool aNotify) +{ + if (aNameSpaceID == kNameSpaceID_None) { + if (aName == nsGkAtoms::disabled) { + UpdateBarredFromConstraintValidation(); + } else if (aName == nsGkAtoms::required) { + UpdateValueMissingValidityState(); + } else if (aName == nsGkAtoms::autocomplete) { + // Clear the cached @autocomplete attribute state + mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown; + } + + UpdateState(aNotify); + } + + return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName, + aValue, aNotify); +} + +nsresult +HTMLSelectElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute, + bool aNotify) +{ + if (aNotify && aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::multiple) { + // We're changing from being a multi-select to a single-select. + // Make sure we only have one option selected before we do that. + // Note that this needs to come before we really unset the attr, + // since SetOptionsSelectedByIndex does some bail-out type + // optimization for cases when the select is not multiple that + // would lead to only a single option getting deselected. + if (mSelectedIndex >= 0) { + SetSelectedIndexInternal(mSelectedIndex, aNotify); + } + } + + nsresult rv = nsGenericHTMLFormElementWithState::UnsetAttr(aNameSpaceID, aAttribute, + aNotify); + NS_ENSURE_SUCCESS(rv, rv); + + if (aNotify && aNameSpaceID == kNameSpaceID_None && + aAttribute == nsGkAtoms::multiple) { + // We might have become a combobox; make sure _something_ gets + // selected in that case + CheckSelectSomething(aNotify); + } + + return rv; +} + +void +HTMLSelectElement::DoneAddingChildren(bool aHaveNotified) +{ + mIsDoneAddingChildren = true; + + nsISelectControlFrame* selectFrame = GetSelectFrame(); + + // If we foolishly tried to restore before we were done adding + // content, restore the rest of the options proper-like + if (mRestoreState) { + RestoreStateTo(mRestoreState); + mRestoreState = nullptr; + } + + // Notify the frame + if (selectFrame) { + selectFrame->DoneAddingChildren(true); + } + + if (!mInhibitStateRestoration) { + nsresult rv = GenerateStateKey(); + if (NS_SUCCEEDED(rv)) { + RestoreFormControlState(); + } + } + + // Now that we're done, select something (if it's a single select something + // must be selected) + if (!CheckSelectSomething(false)) { + // If an option has @selected set, it will be selected during parsing but + // with an empty value. We have to make sure the select element updates it's + // validity state to take this into account. + UpdateValueMissingValidityState(); + + // And now make sure we update our content state too + UpdateState(aHaveNotified); + } + + mDefaultSelectionSet = true; +} + +bool +HTMLSelectElement::ParseAttribute(int32_t aNamespaceID, + nsIAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult) +{ + if (kNameSpaceID_None == aNamespaceID) { + if (aAttribute == nsGkAtoms::size) { + return aResult.ParsePositiveIntValue(aValue); + } else if (aAttribute == nsGkAtoms::autocomplete) { + aResult.ParseAtomArray(aValue); + return true; + } + } + return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue, + aResult); +} + +void +HTMLSelectElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes, + nsRuleData* aData) +{ + nsGenericHTMLFormElementWithState::MapImageAlignAttributeInto(aAttributes, aData); + nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes, aData); +} + +nsChangeHint +HTMLSelectElement::GetAttributeChangeHint(const nsIAtom* aAttribute, + int32_t aModType) const +{ + nsChangeHint retval = + nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType); + if (aAttribute == nsGkAtoms::multiple || + aAttribute == nsGkAtoms::size) { + retval |= nsChangeHint_ReconstructFrame; + } + return retval; +} + +NS_IMETHODIMP_(bool) +HTMLSelectElement::IsAttributeMapped(const nsIAtom* aAttribute) const +{ + static const MappedAttributeEntry* const map[] = { + sCommonAttributeMap, + sImageAlignAttributeMap + }; + + return FindAttributeDependence(aAttribute, map); +} + +nsMapRuleToAttributesFunc +HTMLSelectElement::GetAttributeMappingFunction() const +{ + return &MapAttributesIntoRule; +} + +bool +HTMLSelectElement::IsDisabledForEvents(EventMessage aMessage) +{ + nsIFormControlFrame* formControlFrame = GetFormControlFrame(false); + nsIFrame* formFrame = nullptr; + if (formControlFrame) { + formFrame = do_QueryFrame(formControlFrame); + } + return IsElementDisabledForEvents(aMessage, formFrame); +} + +nsresult +HTMLSelectElement::PreHandleEvent(EventChainPreVisitor& aVisitor) +{ + aVisitor.mCanHandle = false; + if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) { + return NS_OK; + } + + return nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor); +} + +nsresult +HTMLSelectElement::PostHandleEvent(EventChainPostVisitor& aVisitor) +{ + if (aVisitor.mEvent->mMessage == eFocus) { + // If the invalid UI is shown, we should show it while focused and + // update the invalid/valid UI. + mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI(); + + // If neither invalid UI nor valid UI is shown, we shouldn't show the valid + // UI while focused. + mCanShowValidUI = ShouldShowValidityUI(); + + // We don't have to update NS_EVENT_STATE_MOZ_UI_INVALID nor + // NS_EVENT_STATE_MOZ_UI_VALID given that the states should not change. + } else if (aVisitor.mEvent->mMessage == eBlur) { + mCanShowInvalidUI = true; + mCanShowValidUI = true; + + UpdateState(true); + } + + return nsGenericHTMLFormElementWithState::PostHandleEvent(aVisitor); +} + +EventStates +HTMLSelectElement::IntrinsicState() const +{ + EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState(); + + if (IsCandidateForConstraintValidation()) { + if (IsValid()) { + state |= NS_EVENT_STATE_VALID; + } else { + state |= NS_EVENT_STATE_INVALID; + + if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) && + (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) || + (mCanShowInvalidUI && ShouldShowValidityUI()))) { + state |= NS_EVENT_STATE_MOZ_UI_INVALID; + } + } + + // :-moz-ui-valid applies if all the following are true: + // 1. The element is not focused, or had either :-moz-ui-valid or + // :-moz-ui-invalid applying before it was focused ; + // 2. The element is either valid or isn't allowed to have + // :-moz-ui-invalid applying ; + // 3. The element has no form owner or its form owner doesn't have the + // novalidate attribute set ; + // 4. The element has already been modified or the user tried to submit the + // form owner while invalid. + if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) && + (mCanShowValidUI && ShouldShowValidityUI() && + (IsValid() || (state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) && + !mCanShowInvalidUI)))) { + state |= NS_EVENT_STATE_MOZ_UI_VALID; + } + } + + if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) { + state |= NS_EVENT_STATE_REQUIRED; + } else { + state |= NS_EVENT_STATE_OPTIONAL; + } + + return state; +} + +// nsIFormControl + +NS_IMETHODIMP +HTMLSelectElement::SaveState() +{ + RefPtr<SelectState> state = new SelectState(); + + uint32_t len = Length(); + + for (uint32_t optIndex = 0; optIndex < len; optIndex++) { + HTMLOptionElement* option = Item(optIndex); + if (option && option->Selected()) { + nsAutoString value; + option->GetValue(value); + state->PutOption(optIndex, value); + } + } + + nsPresState* presState = GetPrimaryPresState(); + if (presState) { + presState->SetStateProperty(state); + + if (mDisabledChanged) { + // We do not want to save the real disabled state but the disabled + // attribute. + presState->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)); + } + } + + return NS_OK; +} + +bool +HTMLSelectElement::RestoreState(nsPresState* aState) +{ + // Get the presentation state object to retrieve our stuff out of. + nsCOMPtr<SelectState> state( + do_QueryInterface(aState->GetStateProperty())); + + if (state) { + RestoreStateTo(state); + + // Don't flush, if the frame doesn't exist yet it doesn't care if + // we're reset or not. + DispatchContentReset(); + } + + if (aState->IsDisabledSet()) { + SetDisabled(aState->GetDisabled()); + } + + return false; +} + +void +HTMLSelectElement::RestoreStateTo(SelectState* aNewSelected) +{ + if (!mIsDoneAddingChildren) { + mRestoreState = aNewSelected; + return; + } + + uint32_t len = Length(); + uint32_t mask = IS_SELECTED | CLEAR_ALL | SET_DISABLED | NOTIFY; + + // First clear all + SetOptionsSelectedByIndex(-1, -1, mask); + + // Next set the proper ones + for (uint32_t i = 0; i < len; i++) { + HTMLOptionElement* option = Item(i); + if (option) { + nsAutoString value; + nsresult rv = option->GetValue(value); + if (NS_SUCCEEDED(rv) && aNewSelected->ContainsOption(i, value)) { + SetOptionsSelectedByIndex(i, i, IS_SELECTED | SET_DISABLED | NOTIFY); + } + } + } +} + +NS_IMETHODIMP +HTMLSelectElement::Reset() +{ + uint32_t numSelected = 0; + + // + // Cycle through the options array and reset the options + // + uint32_t numOptions = Length(); + + for (uint32_t i = 0; i < numOptions; i++) { + RefPtr<HTMLOptionElement> option = Item(i); + if (option) { + // + // Reset the option to its default value + // + + uint32_t mask = SET_DISABLED | NOTIFY; + if (option->DefaultSelected()) { + mask |= IS_SELECTED; + numSelected++; + } + + SetOptionsSelectedByIndex(i, i, mask); + } + } + + // + // If nothing was selected and it's not multiple, select something + // + if (numSelected == 0 && IsCombobox()) { + SelectSomething(true); + } + + SetSelectionChanged(false, true); + + // + // Let the frame know we were reset + // + // Don't flush, if there's no frame yet it won't care about us being + // reset even if we forced it to be created now. + // + DispatchContentReset(); + + return NS_OK; +} + +static NS_DEFINE_CID(kFormProcessorCID, NS_FORMPROCESSOR_CID); + +NS_IMETHODIMP +HTMLSelectElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission) +{ + // Disabled elements don't submit + if (IsDisabled()) { + return NS_OK; + } + + // + // Get the name (if no name, no submit) + // + nsAutoString name; + GetAttr(kNameSpaceID_None, nsGkAtoms::name, name); + if (name.IsEmpty()) { + return NS_OK; + } + + // + // Submit + // + uint32_t len = Length(); + + nsAutoString mozType; + nsCOMPtr<nsIFormProcessor> keyGenProcessor; + if (GetAttr(kNameSpaceID_None, nsGkAtoms::moztype, mozType) && + mozType.EqualsLiteral("-mozilla-keygen")) { + keyGenProcessor = do_GetService(kFormProcessorCID); + } + + for (uint32_t optIndex = 0; optIndex < len; optIndex++) { + HTMLOptionElement* option = Item(optIndex); + + // Don't send disabled options + if (!option || IsOptionDisabled(option)) { + continue; + } + + if (!option->Selected()) { + continue; + } + + nsString value; + MOZ_ALWAYS_SUCCEEDS(option->GetValue(value)); + + if (keyGenProcessor) { + nsString tmp(value); + if (NS_SUCCEEDED(keyGenProcessor->ProcessValue(this, name, tmp))) { + value = tmp; + } + } + + aFormSubmission->AddNameValuePair(name, value); + } + + return NS_OK; +} + +void +HTMLSelectElement::DispatchContentReset() +{ + nsIFormControlFrame* formControlFrame = GetFormControlFrame(false); + if (formControlFrame) { + // Only dispatch content reset notification if this is a list control + // frame or combo box control frame. + if (IsCombobox()) { + nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame); + if (comboFrame) { + comboFrame->OnContentReset(); + } + } else { + nsIListControlFrame* listFrame = do_QueryFrame(formControlFrame); + if (listFrame) { + listFrame->OnContentReset(); + } + } + } +} + +static void +AddOptions(nsIContent* aRoot, HTMLOptionsCollection* aArray) +{ + for (nsIContent* child = aRoot->GetFirstChild(); + child; + child = child->GetNextSibling()) { + HTMLOptionElement* opt = HTMLOptionElement::FromContent(child); + if (opt) { + aArray->AppendOption(opt); + } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) { + for (nsIContent* grandchild = child->GetFirstChild(); + grandchild; + grandchild = grandchild->GetNextSibling()) { + opt = HTMLOptionElement::FromContent(grandchild); + if (opt) { + aArray->AppendOption(opt); + } + } + } + } +} + +void +HTMLSelectElement::RebuildOptionsArray(bool aNotify) +{ + mOptions->Clear(); + AddOptions(this, mOptions); + FindSelectedIndex(0, aNotify); +} + +bool +HTMLSelectElement::IsValueMissing() +{ + if (!Required()) { + return false; + } + + uint32_t length = Length(); + + for (uint32_t i = 0; i < length; ++i) { + RefPtr<HTMLOptionElement> option = Item(i); + if (!option->Selected()) { + continue; + } + + if (IsOptionDisabled(option)) { + continue; + } + + nsAutoString value; + MOZ_ALWAYS_SUCCEEDS(option->GetValue(value)); + if (!value.IsEmpty()) { + return false; + } + } + + return true; +} + +void +HTMLSelectElement::UpdateValueMissingValidityState() +{ + SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing()); +} + +nsresult +HTMLSelectElement::GetValidationMessage(nsAString& aValidationMessage, + ValidityStateType aType) +{ + switch (aType) { + case VALIDITY_STATE_VALUE_MISSING: { + nsXPIDLString message; + nsresult rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "FormValidationSelectMissing", + message); + aValidationMessage = message; + return rv; + } + default: { + return nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType); + } + } +} + +#ifdef DEBUG + +void +HTMLSelectElement::VerifyOptionsArray() +{ + int32_t index = 0; + for (nsIContent* child = nsINode::GetFirstChild(); + child; + child = child->GetNextSibling()) { + HTMLOptionElement* opt = HTMLOptionElement::FromContent(child); + if (opt) { + NS_ASSERTION(opt == mOptions->ItemAsOption(index++), + "Options collection broken"); + } else if (child->IsHTMLElement(nsGkAtoms::optgroup)) { + for (nsIContent* grandchild = child->GetFirstChild(); + grandchild; + grandchild = grandchild->GetNextSibling()) { + opt = HTMLOptionElement::FromContent(grandchild); + if (opt) { + NS_ASSERTION(opt == mOptions->ItemAsOption(index++), + "Options collection broken"); + } + } + } + } +} + +#endif + +void +HTMLSelectElement::UpdateBarredFromConstraintValidation() +{ + SetBarredFromConstraintValidation(IsDisabled()); +} + +void +HTMLSelectElement::FieldSetDisabledChanged(bool aNotify) +{ + UpdateBarredFromConstraintValidation(); + + nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify); +} + +void +HTMLSelectElement::SetSelectionChanged(bool aValue, bool aNotify) +{ + if (!mDefaultSelectionSet) { + return; + } + + UpdateSelectedOptions(); + + bool previousSelectionChangedValue = mSelectionHasChanged; + mSelectionHasChanged = aValue; + + if (mSelectionHasChanged != previousSelectionChangedValue) { + UpdateState(aNotify); + } +} + +void +HTMLSelectElement::UpdateSelectedOptions() +{ + if (mSelectedOptions) { + mSelectedOptions->SetDirty(); + } +} + +bool +HTMLSelectElement::OpenInParentProcess() +{ + nsIFormControlFrame* formControlFrame = GetFormControlFrame(false); + nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame); + if (comboFrame) { + return comboFrame->IsOpenInParentProcess(); + } + + return false; +} + +void +HTMLSelectElement::SetOpenInParentProcess(bool aVal) +{ + nsIFormControlFrame* formControlFrame = GetFormControlFrame(false); + nsIComboboxControlFrame* comboFrame = do_QueryFrame(formControlFrame); + if (comboFrame) { + comboFrame->SetOpenInParentProcess(aVal); + } +} + +JSObject* +HTMLSelectElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return HTMLSelectElementBinding::Wrap(aCx, this, aGivenProto); +} + +} // namespace dom +} // namespace mozilla |