summaryrefslogtreecommitdiffstats
path: root/dom/html/HTMLSelectElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/HTMLSelectElement.cpp')
-rw-r--r--dom/html/HTMLSelectElement.cpp1917
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