forked from mirrors/gecko-dev
		
	 685cf4c3d8
			
		
	
	
		685cf4c3d8
		
	
	
	
	
		
			
			MozReview-Commit-ID: IIOT9jq8hOl --HG-- extra : rebase_source : dfb3d874ce4419f32f13e662bd495e97ac9cac10
		
			
				
	
	
		
			458 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			458 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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
 |