HTMLOptionElement.cpp (10728B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/HTMLOptionElement.h" 8 9 #include "HTMLOptGroupElement.h" 10 #include "mozilla/dom/HTMLOptionElementBinding.h" 11 #include "mozilla/dom/HTMLSelectElement.h" 12 #include "nsGkAtoms.h" 13 #include "nsIFormControl.h" 14 #include "nsISelectControlFrame.h" 15 #include "nsStyleConsts.h" 16 17 // Notify/query select frame for selected state 18 #include "mozAutoDocUpdate.h" 19 #include "mozilla/dom/Document.h" 20 #include "nsCOMPtr.h" 21 #include "nsContentCreatorFunctions.h" 22 #include "nsNodeInfoManager.h" 23 #include "nsTextNode.h" 24 25 /** 26 * Implementation of <option> 27 */ 28 29 NS_IMPL_NS_NEW_HTML_ELEMENT(Option) 30 31 namespace mozilla::dom { 32 33 HTMLOptionElement::HTMLOptionElement( 34 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 35 : nsGenericHTMLElement(std::move(aNodeInfo)) { 36 // We start off enabled 37 AddStatesSilently(ElementState::ENABLED); 38 } 39 40 HTMLOptionElement::~HTMLOptionElement() = default; 41 42 NS_IMPL_ELEMENT_CLONE(HTMLOptionElement) 43 44 mozilla::dom::HTMLFormElement* HTMLOptionElement::GetForm() { 45 HTMLSelectElement* selectControl = GetSelect(); 46 return selectControl ? selectControl->GetForm() : nullptr; 47 } 48 49 void HTMLOptionElement::SetSelectedInternal(bool aValue, bool aNotify) { 50 mSelectedChanged = true; 51 SetStates(ElementState::CHECKED, aValue, aNotify); 52 } 53 54 void HTMLOptionElement::OptGroupDisabledChanged(bool aNotify) { 55 UpdateDisabledState(aNotify); 56 } 57 58 void HTMLOptionElement::UpdateDisabledState(bool aNotify) { 59 bool isDisabled = HasAttr(nsGkAtoms::disabled); 60 61 if (!isDisabled) { 62 nsIContent* parent = GetParent(); 63 if (auto optGroupElement = HTMLOptGroupElement::FromNodeOrNull(parent)) { 64 isDisabled = optGroupElement->IsDisabled(); 65 } 66 } 67 68 ElementState disabledStates; 69 if (isDisabled) { 70 disabledStates |= ElementState::DISABLED; 71 } else { 72 disabledStates |= ElementState::ENABLED; 73 } 74 75 ElementState oldDisabledStates = State() & ElementState::DISABLED_STATES; 76 ElementState changedStates = disabledStates ^ oldDisabledStates; 77 78 if (!changedStates.IsEmpty()) { 79 ToggleStates(changedStates, aNotify); 80 } 81 } 82 83 void HTMLOptionElement::SetSelected(bool aValue) { 84 // Note: The select content obj maintains all the PresState 85 // so defer to it to get the answer 86 HTMLSelectElement* selectInt = GetSelect(); 87 if (selectInt) { 88 int32_t index = Index(); 89 HTMLSelectElement::OptionFlags mask{ 90 HTMLSelectElement::OptionFlag::SetDisabled, 91 HTMLSelectElement::OptionFlag::Notify}; 92 if (aValue) { 93 mask += HTMLSelectElement::OptionFlag::IsSelected; 94 } 95 96 // This should end up calling SetSelectedInternal 97 selectInt->SetOptionsSelectedByIndex(index, index, mask); 98 } else { 99 SetSelectedInternal(aValue, true); 100 } 101 } 102 103 int32_t HTMLOptionElement::Index() { 104 static int32_t defaultIndex = 0; 105 106 // Only select elements can contain a list of options. 107 HTMLSelectElement* selectElement = GetSelect(); 108 if (!selectElement) { 109 return defaultIndex; 110 } 111 112 HTMLOptionsCollection* options = selectElement->GetOptions(); 113 if (!options) { 114 return defaultIndex; 115 } 116 117 int32_t index = defaultIndex; 118 MOZ_ALWAYS_SUCCEEDS(options->GetOptionIndex(this, 0, true, &index)); 119 return index; 120 } 121 122 nsChangeHint HTMLOptionElement::GetAttributeChangeHint( 123 const nsAtom* aAttribute, AttrModType aModType) const { 124 nsChangeHint retval = 125 nsGenericHTMLElement::GetAttributeChangeHint(aAttribute, aModType); 126 127 if (aAttribute == nsGkAtoms::label) { 128 retval |= nsChangeHint_ReconstructFrame; 129 } else if (aAttribute == nsGkAtoms::text) { 130 retval |= NS_STYLE_HINT_REFLOW; 131 } 132 return retval; 133 } 134 135 void HTMLOptionElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, 136 const nsAttrValue* aValue, bool aNotify) { 137 nsGenericHTMLElement::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify); 138 139 if (aNamespaceID != kNameSpaceID_None || aName != nsGkAtoms::selected || 140 mSelectedChanged) { 141 return; 142 } 143 144 // We just changed out selected state (since we look at the "selected" 145 // attribute when mSelectedChanged is false). Let's tell our select about 146 // it. 147 HTMLSelectElement* selectInt = GetSelect(); 148 if (!selectInt) { 149 // If option is a child of select, SetOptionsSelectedByIndex will set the 150 // selected state if needed. 151 SetStates(ElementState::CHECKED, !!aValue, aNotify); 152 return; 153 } 154 155 NS_ASSERTION(!mSelectedChanged, "Shouldn't be here"); 156 157 bool inSetDefaultSelected = mIsInSetDefaultSelected; 158 mIsInSetDefaultSelected = true; 159 160 int32_t index = Index(); 161 HTMLSelectElement::OptionFlags mask = 162 HTMLSelectElement::OptionFlag::SetDisabled; 163 if (aValue) { 164 mask += HTMLSelectElement::OptionFlag::IsSelected; 165 } 166 167 if (aNotify) { 168 mask += HTMLSelectElement::OptionFlag::Notify; 169 } 170 171 // This can end up calling SetSelectedInternal if our selected state needs to 172 // change, which we will allow to take effect so that parts of 173 // SetOptionsSelectedByIndex that might depend on it working don't get 174 // confused. 175 selectInt->SetOptionsSelectedByIndex(index, index, mask); 176 177 // Now reset our members; when we finish the attr set we'll end up with the 178 // rigt selected state. 179 mIsInSetDefaultSelected = inSetDefaultSelected; 180 // the selected state might have been changed by SetOptionsSelectedByIndex, 181 // possibly more than once; make sure our mSelectedChanged state is set back 182 // correctly. 183 mSelectedChanged = false; 184 } 185 186 void HTMLOptionElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 187 const nsAttrValue* aValue, 188 const nsAttrValue* aOldValue, 189 nsIPrincipal* aSubjectPrincipal, 190 bool aNotify) { 191 if (aNameSpaceID == kNameSpaceID_None) { 192 if (aName == nsGkAtoms::disabled) { 193 UpdateDisabledState(aNotify); 194 } 195 196 if (aName == nsGkAtoms::value && Selected()) { 197 // Since this option is selected, changing value may have changed missing 198 // validity state of the select element 199 if (HTMLSelectElement* select = GetSelect()) { 200 select->UpdateValueMissingValidityState(); 201 } 202 } 203 204 if (aName == nsGkAtoms::selected) { 205 SetStates(ElementState::DEFAULT, !!aValue, aNotify); 206 } 207 } 208 209 return nsGenericHTMLElement::AfterSetAttr( 210 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); 211 } 212 213 void HTMLOptionElement::GetText(nsAString& aText) { 214 nsAutoString text; 215 216 nsIContent* child = nsINode::GetFirstChild(); 217 while (child) { 218 if (Text* textChild = child->GetAsText()) { 219 textChild->AppendTextTo(text); 220 } 221 if (child->IsHTMLElement(nsGkAtoms::script) || 222 child->IsSVGElement(nsGkAtoms::script)) { 223 child = child->GetNextNonChildNode(this); 224 } else { 225 child = child->GetNextNode(this); 226 } 227 } 228 229 // XXX No CompressWhitespace for nsAString. Sad. 230 text.CompressWhitespace(true, true); 231 aText = text; 232 } 233 234 void HTMLOptionElement::SetText(const nsAString& aText, ErrorResult& aRv) { 235 aRv = nsContentUtils::SetNodeTextContent(this, aText, false); 236 } 237 238 nsresult HTMLOptionElement::BindToTree(BindContext& aContext, 239 nsINode& aParent) { 240 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent); 241 NS_ENSURE_SUCCESS(rv, rv); 242 243 // Our new parent might change :disabled/:enabled state. 244 UpdateDisabledState(false); 245 246 return NS_OK; 247 } 248 249 void HTMLOptionElement::UnbindFromTree(UnbindContext& aContext) { 250 nsGenericHTMLElement::UnbindFromTree(aContext); 251 252 // Our previous parent could have been involved in :disabled/:enabled state. 253 UpdateDisabledState(false); 254 } 255 256 // Get the select content element that contains this option 257 HTMLSelectElement* HTMLOptionElement::GetSelect() const { 258 nsIContent* parent = GetParent(); 259 if (!parent) { 260 return nullptr; 261 } 262 263 HTMLSelectElement* select = HTMLSelectElement::FromNode(parent); 264 if (select) { 265 return select; 266 } 267 268 if (!parent->IsHTMLElement(nsGkAtoms::optgroup)) { 269 return nullptr; 270 } 271 272 return HTMLSelectElement::FromNodeOrNull(parent->GetParent()); 273 } 274 275 already_AddRefed<HTMLOptionElement> HTMLOptionElement::Option( 276 const GlobalObject& aGlobal, const nsAString& aText, 277 const Optional<nsAString>& aValue, bool aDefaultSelected, bool aSelected, 278 ErrorResult& aError) { 279 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aGlobal.GetAsSupports()); 280 Document* doc; 281 if (!win || !(doc = win->GetExtantDoc())) { 282 aError.Throw(NS_ERROR_FAILURE); 283 return nullptr; 284 } 285 286 RefPtr<mozilla::dom::NodeInfo> nodeInfo = doc->NodeInfoManager()->GetNodeInfo( 287 nsGkAtoms::option, nullptr, kNameSpaceID_XHTML, ELEMENT_NODE); 288 289 auto* nim = nodeInfo->NodeInfoManager(); 290 RefPtr<HTMLOptionElement> option = 291 new (nim) HTMLOptionElement(nodeInfo.forget()); 292 293 if (!aText.IsEmpty()) { 294 // Create a new text node and append it to the option 295 RefPtr<nsTextNode> textContent = new (option->NodeInfo()->NodeInfoManager()) 296 nsTextNode(option->NodeInfo()->NodeInfoManager()); 297 298 textContent->SetText(aText, false); 299 300 option->AppendChildTo(textContent, false, aError); 301 if (aError.Failed()) { 302 return nullptr; 303 } 304 } 305 306 if (aValue.WasPassed()) { 307 // Set the value attribute for this element. We're calling SetAttr 308 // directly because we want to pass aNotify == false. 309 aError = option->SetAttr(kNameSpaceID_None, nsGkAtoms::value, 310 aValue.Value(), false); 311 if (aError.Failed()) { 312 return nullptr; 313 } 314 } 315 316 if (aDefaultSelected) { 317 // We're calling SetAttr directly because we want to pass 318 // aNotify == false. 319 aError = 320 option->SetAttr(kNameSpaceID_None, nsGkAtoms::selected, u""_ns, false); 321 if (aError.Failed()) { 322 return nullptr; 323 } 324 } 325 326 option->SetSelected(aSelected); 327 option->SetSelectedChanged(false); 328 329 return option.forget(); 330 } 331 332 nsresult HTMLOptionElement::CopyInnerTo(Element* aDest) { 333 nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest); 334 NS_ENSURE_SUCCESS(rv, rv); 335 336 if (aDest->OwnerDoc()->IsStaticDocument()) { 337 static_cast<HTMLOptionElement*>(aDest)->SetSelected(Selected()); 338 } 339 return NS_OK; 340 } 341 342 JSObject* HTMLOptionElement::WrapNode(JSContext* aCx, 343 JS::Handle<JSObject*> aGivenProto) { 344 return HTMLOptionElement_Binding::Wrap(aCx, this, aGivenProto); 345 } 346 347 } // namespace mozilla::dom