summaryrefslogtreecommitdiffstats
path: root/dom/html/HTMLOptionElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/HTMLOptionElement.cpp')
-rw-r--r--dom/html/HTMLOptionElement.cpp458
1 files changed, 458 insertions, 0 deletions
diff --git a/dom/html/HTMLOptionElement.cpp b/dom/html/HTMLOptionElement.cpp
new file mode 100644
index 000000000..7b1e3ca2d
--- /dev/null
+++ b/dom/html/HTMLOptionElement.cpp
@@ -0,0 +1,458 @@
+/* -*- 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/HTMLOptionElement.h"
+#include "mozilla/dom/HTMLOptionElementBinding.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "nsIDOMHTMLOptGroupElement.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIFormControl.h"
+#include "nsIForm.h"
+#include "nsIDOMNode.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsISelectControlFrame.h"
+
+// Notify/query select frame for selected state
+#include "nsIFormControlFrame.h"
+#include "nsIDocument.h"
+#include "nsIDOMHTMLSelectElement.h"
+#include "nsNodeInfoManager.h"
+#include "nsCOMPtr.h"
+#include "mozilla/EventStates.h"
+#include "nsContentCreatorFunctions.h"
+#include "mozAutoDocUpdate.h"
+#include "nsTextNode.h"
+
+/**
+ * Implementation of <option>
+ */
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Option)
+
+namespace mozilla {
+namespace dom {
+
+HTMLOptionElement::HTMLOptionElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo),
+ mSelectedChanged(false),
+ mIsSelected(false),
+ mIsInSetDefaultSelected(false)
+{
+ // We start off enabled
+ AddStatesSilently(NS_EVENT_STATE_ENABLED);
+}
+
+HTMLOptionElement::~HTMLOptionElement()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(HTMLOptionElement, nsGenericHTMLElement,
+ nsIDOMHTMLOptionElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLOptionElement)
+
+
+NS_IMETHODIMP
+HTMLOptionElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ NS_IF_ADDREF(*aForm = GetForm());
+ return NS_OK;
+}
+
+mozilla::dom::HTMLFormElement*
+HTMLOptionElement::GetForm()
+{
+ HTMLSelectElement* selectControl = GetSelect();
+ return selectControl ? selectControl->GetForm() : nullptr;
+}
+
+void
+HTMLOptionElement::SetSelectedInternal(bool aValue, bool aNotify)
+{
+ mSelectedChanged = true;
+ mIsSelected = aValue;
+
+ // When mIsInSetDefaultSelected is true, the state change will be handled by
+ // SetAttr/UnsetAttr.
+ if (!mIsInSetDefaultSelected) {
+ UpdateState(aNotify);
+ }
+}
+
+NS_IMETHODIMP
+HTMLOptionElement::GetSelected(bool* aValue)
+{
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = Selected();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLOptionElement::SetSelected(bool aValue)
+{
+ // Note: The select content obj maintains all the PresState
+ // so defer to it to get the answer
+ HTMLSelectElement* selectInt = GetSelect();
+ if (selectInt) {
+ int32_t index = Index();
+ uint32_t mask = HTMLSelectElement::SET_DISABLED | HTMLSelectElement::NOTIFY;
+ if (aValue) {
+ mask |= HTMLSelectElement::IS_SELECTED;
+ }
+
+ // This should end up calling SetSelectedInternal
+ selectInt->SetOptionsSelectedByIndex(index, index, mask);
+ } else {
+ SetSelectedInternal(aValue, true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_BOOL_ATTR(HTMLOptionElement, DefaultSelected, selected)
+// GetText returns a whitespace compressed .textContent value.
+NS_IMPL_STRING_ATTR_WITH_FALLBACK(HTMLOptionElement, Label, label, GetText)
+NS_IMPL_STRING_ATTR_WITH_FALLBACK(HTMLOptionElement, Value, value, GetText)
+NS_IMPL_BOOL_ATTR(HTMLOptionElement, Disabled, disabled)
+
+NS_IMETHODIMP
+HTMLOptionElement::GetIndex(int32_t* aIndex)
+{
+ *aIndex = Index();
+ return NS_OK;
+}
+
+int32_t
+HTMLOptionElement::Index()
+{
+ static int32_t defaultIndex = 0;
+
+ // Only select elements can contain a list of options.
+ HTMLSelectElement* selectElement = GetSelect();
+ if (!selectElement) {
+ return defaultIndex;
+ }
+
+ HTMLOptionsCollection* options = selectElement->GetOptions();
+ if (!options) {
+ return defaultIndex;
+ }
+
+ int32_t index = defaultIndex;
+ MOZ_ALWAYS_SUCCEEDS(options->GetOptionIndex(this, 0, true, &index));
+ return index;
+}
+
+bool
+HTMLOptionElement::Selected() const
+{
+ // If we haven't been explictly selected or deselected, use our default value
+ if (!mSelectedChanged) {
+ return DefaultSelected();
+ }
+
+ return mIsSelected;
+}
+
+bool
+HTMLOptionElement::DefaultSelected() const
+{
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::selected);
+}
+
+nsChangeHint
+HTMLOptionElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
+ int32_t aModType) const
+{
+ nsChangeHint retval =
+ nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType);
+
+ if (aAttribute == nsGkAtoms::label ||
+ aAttribute == nsGkAtoms::text) {
+ retval |= NS_STYLE_HINT_REFLOW;
+ }
+ return retval;
+}
+
+nsresult
+HTMLOptionElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+ nsresult rv = nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName,
+ aValue, aNotify);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::selected ||
+ mSelectedChanged) {
+ return NS_OK;
+ }
+
+ bool defaultSelected = aValue;
+ // First make sure we actually set our mIsSelected state to reflect our new
+ // defaultSelected state. If that turns out to be wrong,
+ // SetOptionsSelectedByIndex will fix it up. But otherwise we can end up in a
+ // situation where mIsSelected is still false, but mSelectedChanged becomes
+ // true (later in this method, when we compare mIsSelected to
+ // defaultSelected), and then we start returning false for Selected() even
+ // though we're actually selected.
+ mIsSelected = defaultSelected;
+
+ // We just changed out selected state (since we look at the "selected"
+ // attribute when mSelectedChanged is false). Let's tell our select about
+ // it.
+ HTMLSelectElement* selectInt = GetSelect();
+ if (!selectInt) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!mSelectedChanged, "Shouldn't be here");
+
+ bool inSetDefaultSelected = mIsInSetDefaultSelected;
+ mIsInSetDefaultSelected = true;
+
+ int32_t index = Index();
+ uint32_t mask = HTMLSelectElement::SET_DISABLED;
+ if (defaultSelected) {
+ mask |= HTMLSelectElement::IS_SELECTED;
+ }
+
+ if (aNotify) {
+ mask |= HTMLSelectElement::NOTIFY;
+ }
+
+ // This can end up calling SetSelectedInternal if our selected state needs to
+ // change, which we will allow to take effect so that parts of
+ // SetOptionsSelectedByIndex that might depend on it working don't get
+ // confused.
+ selectInt->SetOptionsSelectedByIndex(index, index, mask);
+
+ // Now reset our members; when we finish the attr set we'll end up with the
+ // rigt selected state.
+ mIsInSetDefaultSelected = inSetDefaultSelected;
+ // mIsSelected might have been changed by SetOptionsSelectedByIndex. Possibly
+ // more than once; make sure our mSelectedChanged state is set correctly.
+ mSelectedChanged = mIsSelected != defaultSelected;
+
+ return NS_OK;
+}
+
+nsresult
+HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None &&
+ aName == nsGkAtoms::value && Selected()) {
+ // Since this option is selected, changing value
+ // may have changed missing validity state of the
+ // Select element
+ HTMLSelectElement* select = GetSelect();
+ if (select) {
+ select->UpdateValueMissingValidityState();
+ }
+ }
+
+ return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+NS_IMETHODIMP
+HTMLOptionElement::GetText(nsAString& aText)
+{
+ nsAutoString text;
+
+ nsIContent* child = nsINode::GetFirstChild();
+ while (child) {
+ if (child->NodeType() == nsIDOMNode::TEXT_NODE ||
+ child->NodeType() == nsIDOMNode::CDATA_SECTION_NODE) {
+ child->AppendTextTo(text);
+ }
+ if (child->IsHTMLElement(nsGkAtoms::script) ||
+ child->IsSVGElement(nsGkAtoms::script)) {
+ child = child->GetNextNonChildNode(this);
+ } else {
+ child = child->GetNextNode(this);
+ }
+ }
+
+ // XXX No CompressWhitespace for nsAString. Sad.
+ text.CompressWhitespace(true, true);
+ aText = text;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLOptionElement::SetText(const nsAString& aText)
+{
+ return nsContentUtils::SetNodeTextContent(this, aText, true);
+}
+
+nsresult
+HTMLOptionElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Our new parent might change :disabled/:enabled state.
+ UpdateState(false);
+
+ return NS_OK;
+}
+
+void
+HTMLOptionElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+
+ // Our previous parent could have been involved in :disabled/:enabled state.
+ UpdateState(false);
+}
+
+EventStates
+HTMLOptionElement::IntrinsicState() const
+{
+ EventStates state = nsGenericHTMLElement::IntrinsicState();
+ if (Selected()) {
+ state |= NS_EVENT_STATE_CHECKED;
+ }
+ if (DefaultSelected()) {
+ state |= NS_EVENT_STATE_DEFAULT;
+ }
+
+ // An <option> is disabled if it has @disabled set or if it's <optgroup> has
+ // @disabled set.
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
+ state |= NS_EVENT_STATE_DISABLED;
+ state &= ~NS_EVENT_STATE_ENABLED;
+ } else {
+ nsIContent* parent = GetParent();
+ if (parent && parent->IsHTMLElement(nsGkAtoms::optgroup) &&
+ parent->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled)) {
+ state |= NS_EVENT_STATE_DISABLED;
+ state &= ~NS_EVENT_STATE_ENABLED;
+ } else {
+ state &= ~NS_EVENT_STATE_DISABLED;
+ state |= NS_EVENT_STATE_ENABLED;
+ }
+ }
+
+ return state;
+}
+
+// Get the select content element that contains this option
+HTMLSelectElement*
+HTMLOptionElement::GetSelect()
+{
+ nsIContent* parent = GetParent();
+ if (!parent) {
+ return nullptr;
+ }
+
+ HTMLSelectElement* select = HTMLSelectElement::FromContent(parent);
+ if (select) {
+ return select;
+ }
+
+ if (!parent->IsHTMLElement(nsGkAtoms::optgroup)) {
+ return nullptr;
+ }
+
+ return HTMLSelectElement::FromContentOrNull(parent->GetParent());
+}
+
+already_AddRefed<HTMLOptionElement>
+HTMLOptionElement::Option(const GlobalObject& aGlobal,
+ const Optional<nsAString>& aText,
+ const Optional<nsAString>& aValue,
+ const Optional<bool>& aDefaultSelected,
+ const Optional<bool>& aSelected, ErrorResult& aError)
+{
+ nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports());
+ nsIDocument* doc;
+ if (!win || !(doc = win->GetExtantDoc())) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ already_AddRefed<mozilla::dom::NodeInfo> nodeInfo =
+ doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::option, nullptr,
+ kNameSpaceID_XHTML,
+ nsIDOMNode::ELEMENT_NODE);
+
+ RefPtr<HTMLOptionElement> option = new HTMLOptionElement(nodeInfo);
+
+ if (aText.WasPassed()) {
+ // Create a new text node and append it to the option
+ RefPtr<nsTextNode> textContent =
+ new nsTextNode(option->NodeInfo()->NodeInfoManager());
+
+ textContent->SetText(aText.Value(), false);
+
+ aError = option->AppendChildTo(textContent, false);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (aValue.WasPassed()) {
+ // Set the value attribute for this element. We're calling SetAttr
+ // directly because we want to pass aNotify == false.
+ aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::value,
+ aValue.Value(), false);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (aDefaultSelected.WasPassed()) {
+ if (aDefaultSelected.Value()) {
+ // We're calling SetAttr directly because we want to pass
+ // aNotify == false.
+ aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::selected,
+ EmptyString(), false);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+
+ if (aSelected.WasPassed()) {
+ option->SetSelected(aSelected.Value(), aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+ }
+ }
+
+ return option.forget();
+}
+
+nsresult
+HTMLOptionElement::CopyInnerTo(Element* aDest)
+{
+ nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDest->OwnerDoc()->IsStaticDocument()) {
+ static_cast<HTMLOptionElement*>(aDest)->SetSelected(Selected());
+ }
+ return NS_OK;
+}
+
+JSObject*
+HTMLOptionElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLOptionElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace dom
+} // namespace mozilla