HTMLSelectElement.h (19565B)
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 #ifndef mozilla_dom_HTMLSelectElement_h 7 #define mozilla_dom_HTMLSelectElement_h 8 9 #include "mozilla/Attributes.h" 10 #include "mozilla/EnumSet.h" 11 #include "mozilla/dom/BindingDeclarations.h" 12 #include "mozilla/dom/ConstraintValidation.h" 13 #include "mozilla/dom/HTMLFormElement.h" 14 #include "mozilla/dom/HTMLOptionsCollection.h" 15 #include "nsCOMPtr.h" 16 #include "nsCheapSets.h" 17 #include "nsContentUtils.h" 18 #include "nsError.h" 19 #include "nsGenericHTMLElement.h" 20 21 class nsContentList; 22 class nsIDOMHTMLOptionElement; 23 class nsIHTMLCollection; 24 class nsISelectControlFrame; 25 26 namespace mozilla { 27 28 class ErrorResult; 29 class EventChainPostVisitor; 30 class EventChainPreVisitor; 31 class SelectContentData; 32 class PresState; 33 34 namespace dom { 35 36 class FormData; 37 class HTMLElementOrLong; 38 class HTMLOptionElementOrHTMLOptGroupElement; 39 class HTMLSelectElement; 40 41 class MOZ_STACK_CLASS SafeOptionListMutation { 42 public: 43 /** 44 * @param aSelect The select element which option list is being mutated. 45 * Can be null. 46 * @param aParent The content object which is being mutated. 47 * @param aKid If not null, a new child element is being inserted to 48 * aParent. Otherwise a child element will be removed. 49 * @param aIndex The index of the content object in the parent. 50 */ 51 SafeOptionListMutation(nsIContent* aSelect, nsIContent* aParent, 52 nsIContent* aKid, uint32_t aIndex, bool aNotify); 53 ~SafeOptionListMutation(); 54 void MutationFailed() { mNeedsRebuild = true; } 55 56 private: 57 static void* operator new(size_t) noexcept(true) { return nullptr; } 58 static void operator delete(void*, size_t) {} 59 /** The select element which option list is being mutated. */ 60 RefPtr<HTMLSelectElement> mSelect; 61 /** true if the current mutation is the first one in the stack. */ 62 bool mTopLevelMutation; 63 /** true if it is known that the option list must be recreated. */ 64 bool mNeedsRebuild; 65 /** Whether we should be notifying when we make various method calls on 66 mSelect */ 67 const bool mNotify; 68 /** The selected option at mutation start. */ 69 RefPtr<HTMLOptionElement> mInitialSelectedOption; 70 /** Option list must be recreated if more than one mutation is detected. */ 71 nsMutationGuard mGuard; 72 }; 73 74 /** 75 * Implementation of <select> 76 */ 77 class HTMLSelectElement final : public nsGenericHTMLFormControlElementWithState, 78 public ConstraintValidation { 79 public: 80 /** 81 * IsSelected whether to set the option(s) to true or false 82 * 83 * ClearAll whether to clear all other options (for example, if you 84 * are normal-clicking on the current option) 85 * 86 * SetDisabled whether it is permissible to set disabled options 87 * (for JavaScript) 88 * 89 * Notify whether to notify frames and such 90 * 91 * NoReselect no need to select something after an option is 92 * deselected (for reset) 93 * 94 * InsertingOptions if an option has just been inserted some bailouts can't 95 * be taken 96 */ 97 enum class OptionFlag : uint8_t { 98 IsSelected, 99 ClearAll, 100 SetDisabled, 101 Notify, 102 NoReselect, 103 InsertingOptions 104 }; 105 using OptionFlags = EnumSet<OptionFlag>; 106 107 using ConstraintValidation::GetValidationMessage; 108 109 explicit HTMLSelectElement( 110 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo, 111 FromParser aFromParser = NOT_FROM_PARSER); 112 113 NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLSelectElement, select) 114 115 // nsISupports 116 NS_DECL_ISUPPORTS_INHERITED 117 118 int32_t TabIndexDefault() override; 119 120 // Element 121 bool IsInteractiveHTMLContent() const override { return true; } 122 123 // WebIdl HTMLSelectElement 124 void GetAutocomplete(DOMString& aValue); 125 void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv) { 126 SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv); 127 } 128 129 void GetAutocompleteInfo(AutocompleteInfo& aInfo); 130 131 // Sets the user interacted flag and fires input/change events if needed. 132 MOZ_CAN_RUN_SCRIPT void UserFinishedInteracting(bool aChanged); 133 134 bool Disabled() const { return GetBoolAttr(nsGkAtoms::disabled); } 135 void SetDisabled(bool aVal, ErrorResult& aRv) { 136 SetHTMLBoolAttr(nsGkAtoms::disabled, aVal, aRv); 137 } 138 bool Multiple() const { return GetBoolAttr(nsGkAtoms::multiple); } 139 void SetMultiple(bool aVal, ErrorResult& aRv) { 140 SetHTMLBoolAttr(nsGkAtoms::multiple, aVal, aRv); 141 } 142 143 void GetName(DOMString& aValue) { GetHTMLAttr(nsGkAtoms::name, aValue); } 144 void SetName(const nsAString& aName, ErrorResult& aRv) { 145 SetHTMLAttr(nsGkAtoms::name, aName, aRv); 146 } 147 bool Required() const { return State().HasState(ElementState::REQUIRED); } 148 void SetRequired(bool aVal, ErrorResult& aRv) { 149 SetHTMLBoolAttr(nsGkAtoms::required, aVal, aRv); 150 } 151 uint32_t Size() const { return GetUnsignedIntAttr(nsGkAtoms::size, 0); } 152 void SetSize(uint32_t aSize, ErrorResult& aRv) { 153 SetUnsignedIntAttr(nsGkAtoms::size, aSize, 0, aRv); 154 } 155 156 void GetType(nsAString& aValue); 157 158 HTMLOptionsCollection* Options() const { return mOptions; } 159 uint32_t Length() const { return mOptions->Length(); } 160 void SetLength(uint32_t aLength, ErrorResult& aRv); 161 Element* IndexedGetter(uint32_t aIdx, bool& aFound) const { 162 return mOptions->IndexedGetter(aIdx, aFound); 163 } 164 HTMLOptionElement* Item(uint32_t aIdx) const { 165 return mOptions->ItemAsOption(aIdx); 166 } 167 HTMLOptionElement* NamedItem(const nsAString& aName) const { 168 return mOptions->GetNamedItem(aName); 169 } 170 void Add(const HTMLOptionElementOrHTMLOptGroupElement& aElement, 171 const Nullable<HTMLElementOrLong>& aBefore, ErrorResult& aRv); 172 void Remove(int32_t aIndex) const; 173 void IndexedSetter(uint32_t aIndex, HTMLOptionElement* aOption, 174 ErrorResult& aRv) { 175 mOptions->IndexedSetter(aIndex, aOption, aRv); 176 } 177 178 static bool MatchSelectedOptions(Element* aElement, int32_t, nsAtom*, void*); 179 180 nsIHTMLCollection* SelectedOptions(); 181 182 int32_t SelectedIndex() const { return mSelectedIndex; } 183 void SetSelectedIndex(int32_t aIdx) { SetSelectedIndexInternal(aIdx, true); } 184 void GetValue(DOMString& aValue) const; 185 void SetValue(const nsAString& aValue); 186 187 // Override SetCustomValidity so we update our state properly when it's called 188 // via bindings. 189 void SetCustomValidity(const nsAString& aError); 190 191 void ShowPicker(ErrorResult& aRv); 192 193 using nsINode::Remove; 194 195 // nsINode 196 JSObject* WrapNode(JSContext*, JS::Handle<JSObject*> aGivenProto) override; 197 198 // nsIContent 199 void GetEventTargetParent(EventChainPreVisitor& aVisitor) override; 200 201 bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable, 202 int32_t* aTabIndex) override; 203 void InsertChildBefore( 204 nsIContent* aKid, nsIContent* aBeforeThis, bool aNotify, ErrorResult& aRv, 205 nsINode* aOldParent = nullptr, 206 MutationEffectOnScript aMutationEffectOnScript = 207 MutationEffectOnScript::DropTrustWorthiness) override; 208 void RemoveChildNode( 209 nsIContent* aKid, bool aNotify, const BatchRemovalState* aState, 210 nsINode* aNewParent = nullptr, 211 MutationEffectOnScript aMutationEffectOnScript = 212 MutationEffectOnScript::DropTrustWorthiness) override; 213 214 // nsGenericHTMLElement 215 bool IsDisabledForEvents(WidgetEvent* aEvent) override; 216 217 // nsGenericHTMLFormElement 218 void SaveState() override; 219 bool RestoreState(PresState* aState) override; 220 221 // Overriden nsIFormControl methods 222 NS_IMETHOD Reset() override; 223 NS_IMETHOD SubmitNamesValues(FormData* aFormData) override; 224 225 void FieldSetDisabledChanged(bool aNotify) override; 226 227 /** 228 * To be called when stuff is added under a child of the select--but *before* 229 * they are actually added. 230 * 231 * @param aOptions the content that was added (usually just an option, but 232 * could be an optgroup node with many child options) 233 * @param aParent the parent the options were added to (could be an optgroup) 234 * @param aContentIndex the index where the options are being added within the 235 * parent (if the parent is an optgroup, the index within the optgroup) 236 */ 237 NS_IMETHOD WillAddOptions(nsIContent* aOptions, nsIContent* aParent, 238 int32_t aContentIndex, bool aNotify); 239 240 /** 241 * To be called when stuff is removed under a child of the select--but 242 * *before* they are actually removed. 243 * 244 * @param aParent the parent the option(s) are being removed from 245 * @param aContentIndex the index of the option(s) within the parent (if the 246 * parent is an optgroup, the index within the optgroup) 247 */ 248 NS_IMETHOD WillRemoveOptions(nsIContent* aParent, int32_t aContentIndex, 249 bool aNotify); 250 251 /** 252 * Checks whether an option is disabled (even if it's part of an optgroup) 253 * 254 * @param aIndex the index of the option to check 255 * @return whether the option is disabled 256 */ 257 NS_IMETHOD IsOptionDisabled(int32_t aIndex, bool* aIsDisabled); 258 bool IsOptionDisabled(HTMLOptionElement* aOption) const; 259 260 /** 261 * Sets multiple options (or just sets startIndex if select is single) 262 * and handles notifications and cleanup and everything under the sun. 263 * When this method exits, the select will be in a consistent state. i.e. 264 * if you set the last option to false, it will select an option anyway. 265 * 266 * @param aStartIndex the first index to set 267 * @param aEndIndex the last index to set (set same as first index for one 268 * option) 269 * @param aOptionsMask determines whether to set, clear all or disable 270 * options and whether frames are to be notified of such. 271 * @return whether any options were actually changed 272 */ 273 bool SetOptionsSelectedByIndex(int32_t aStartIndex, int32_t aEndIndex, 274 OptionFlags aOptionsMask); 275 276 /** 277 * Called when an attribute is about to be changed 278 */ 279 nsresult BindToTree(BindContext&, nsINode& aParent) override; 280 void UnbindFromTree(UnbindContext&) override; 281 void BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, 282 const nsAttrValue* aValue, bool aNotify) override; 283 void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, 284 const nsAttrValue* aValue, const nsAttrValue* aOldValue, 285 nsIPrincipal* aSubjectPrincipal, bool aNotify) override; 286 287 void DoneAddingChildren(bool aHaveNotified) override; 288 bool IsDoneAddingChildren() const { return mIsDoneAddingChildren; } 289 290 bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, 291 const nsAString& aValue, 292 nsIPrincipal* aMaybeScriptedPrincipal, 293 nsAttrValue& aResult) override; 294 nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override; 295 nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute, 296 AttrModType aModType) const override; 297 NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; 298 299 nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; 300 301 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( 302 HTMLSelectElement, nsGenericHTMLFormControlElementWithState) 303 304 HTMLOptionsCollection* GetOptions() { return mOptions; } 305 306 // ConstraintValidation 307 nsresult GetValidationMessage(nsAString& aValidationMessage, 308 ValidityStateType aType) override; 309 310 void UpdateValueMissingValidityState(); 311 void UpdateValidityElementStates(bool aNotify); 312 /** 313 * Insert aElement before the node given by aBefore 314 */ 315 void Add(nsGenericHTMLElement& aElement, nsGenericHTMLElement* aBefore, 316 ErrorResult& aError); 317 void Add(nsGenericHTMLElement& aElement, int32_t aIndex, 318 ErrorResult& aError) { 319 // If item index is out of range, insert to last. 320 // (since beforeElement becomes null, it is inserted to last) 321 nsIContent* beforeContent = mOptions->GetElementAt(aIndex); 322 return Add(aElement, nsGenericHTMLElement::FromNodeOrNull(beforeContent), 323 aError); 324 } 325 326 /** 327 * Is this a combobox? 328 */ 329 bool IsCombobox() const { return !Multiple() && Size() <= 1; } 330 331 bool OpenInParentProcess() const { return mIsOpenInParentProcess; } 332 void SetOpenInParentProcess(bool aVal) { 333 mIsOpenInParentProcess = aVal; 334 SetStates(ElementState::OPEN, aVal); 335 } 336 337 void GetPreviewValue(nsAString& aValue) { aValue = mPreviewValue; } 338 void SetPreviewValue(const nsAString& aValue); 339 340 void SetAutofillState(const nsAString& aState) { 341 SetFormAutofillState(aState); 342 } 343 void GetAutofillState(nsAString& aState) { GetFormAutofillState(aState); } 344 345 protected: 346 virtual ~HTMLSelectElement() = default; 347 348 friend class SafeOptionListMutation; 349 350 // Helper Methods 351 /** 352 * Check whether the option specified by the index is selected 353 * @param aIndex the index 354 * @return whether the option at the index is selected 355 */ 356 bool IsOptionSelectedByIndex(int32_t aIndex) const; 357 /** 358 * Starting with (and including) aStartIndex, find the first selected index 359 * and set mSelectedIndex to it. 360 * @param aStartIndex the index to start with 361 */ 362 void FindSelectedIndex(int32_t aStartIndex, bool aNotify); 363 /** 364 * Select some option if possible (generally the first non-disabled option). 365 * @return true if something was selected, false otherwise 366 */ 367 bool SelectSomething(bool aNotify); 368 /** 369 * Call SelectSomething(), but only if nothing is selected 370 * @see SelectSomething() 371 * @return true if something was selected, false otherwise 372 */ 373 bool CheckSelectSomething(bool aNotify); 374 /** 375 * Called to trigger notifications of frames and fixing selected index 376 * 377 * @param aSelectFrame the frame for this content (could be null) 378 * @param aIndex the index that was selected or deselected 379 * @param aSelected whether the index was selected or deselected 380 * @param aChangeOptionState if false, don't do anything to the 381 * HTMLOptionElement at aIndex. If true, change 382 * its selected state to aSelected. 383 * @param aNotify whether to notify the style system and such 384 */ 385 void OnOptionSelected(nsISelectControlFrame* aSelectFrame, int32_t aIndex, 386 bool aSelected, bool aChangeOptionState, bool aNotify); 387 /** 388 * Restore state to a particular state string (representing the options) 389 * @param aNewSelected the state string to restore to 390 */ 391 void RestoreStateTo(const SelectContentData& aNewSelected); 392 393 // Adding options 394 /** 395 * Insert option(s) into the options[] array and perform notifications 396 * @param aOptions the option or optgroup being added 397 * @param aListIndex the index to start adding options into the list at 398 * @param aDepth the depth of aOptions (1=direct child of select ...) 399 */ 400 void InsertOptionsIntoList(nsIContent* aOptions, int32_t aListIndex, 401 int32_t aDepth, bool aNotify); 402 /** 403 * Remove option(s) from the options[] array 404 * @param aOptions the option or optgroup being added 405 * @param aListIndex the index to start removing options from the list at 406 * @param aDepth the depth of aOptions (1=direct child of select ...) 407 */ 408 nsresult RemoveOptionsFromList(nsIContent* aOptions, int32_t aListIndex, 409 int32_t aDepth, bool aNotify); 410 411 // nsIConstraintValidation 412 void UpdateBarredFromConstraintValidation(); 413 bool IsValueMissing() const; 414 415 /** 416 * Get the index of the first option at, under or following the content in 417 * the select, or length of options[] if none are found 418 * @param aOptions the content 419 * @return the index of the first option 420 */ 421 int32_t GetOptionIndexAt(nsIContent* aOptions); 422 /** 423 * Get the next option following the content in question (not at or under) 424 * (this could include siblings of the current content or siblings of the 425 * parent or children of siblings of the parent). 426 * @param aOptions the content 427 * @return the index of the next option after the content 428 */ 429 int32_t GetOptionIndexAfter(nsIContent* aOptions); 430 /** 431 * Get the first option index at or under the content in question. 432 * @param aOptions the content 433 * @return the index of the first option at or under the content 434 */ 435 int32_t GetFirstOptionIndex(nsIContent* aOptions); 436 /** 437 * Get the first option index under the content in question, within the 438 * range specified. 439 * @param aOptions the content 440 * @param aStartIndex the first child to look at 441 * @param aEndIndex the child *after* the last child to look at 442 * @return the index of the first option at or under the content 443 */ 444 int32_t GetFirstChildOptionIndex(nsIContent* aOptions, int32_t aStartIndex, 445 int32_t aEndIndex); 446 447 /** 448 * Get the frame as an nsISelectControlFrame (MAY RETURN nullptr) 449 * @return the select frame, or null 450 */ 451 nsISelectControlFrame* GetSelectFrame(); 452 453 /** 454 * Helper method for dispatching ContentReset notifications to list box 455 * frames. 456 */ 457 void DispatchContentReset(); 458 459 /** 460 * Rebuilds the options array from scratch as a fallback in error cases. 461 */ 462 void RebuildOptionsArray(bool aNotify); 463 464 #ifdef DEBUG 465 void VerifyOptionsArray(); 466 #endif 467 468 void SetSelectedIndexInternal(int32_t aIndex, bool aNotify); 469 470 void OnSelectionChanged(); 471 472 /** 473 * Marks the selectedOptions list as dirty, so that it'll populate itself 474 * again. 475 */ 476 void UpdateSelectedOptions(); 477 478 void SetUserInteracted(bool) final; 479 480 /** The options[] array */ 481 RefPtr<HTMLOptionsCollection> mOptions; 482 nsContentUtils::AutocompleteAttrState mAutocompleteAttrState; 483 nsContentUtils::AutocompleteAttrState mAutocompleteInfoState; 484 /** false if the parser is in the middle of adding children. */ 485 bool mIsDoneAddingChildren : 1; 486 /** true if our disabled state has changed from the default **/ 487 bool mDisabledChanged : 1; 488 /** true if child nodes are being added or removed. 489 * Used by SafeOptionListMutation. 490 */ 491 bool mMutating : 1; 492 /** 493 * True if DoneAddingChildren will get called but shouldn't restore state. 494 */ 495 bool mInhibitStateRestoration : 1; 496 /** https://html.spec.whatwg.org/#user-interacted */ 497 bool mUserInteracted : 1; 498 /** True if the default selected option has been set. */ 499 bool mDefaultSelectionSet : 1; 500 /** True if we're open in the parent process */ 501 bool mIsOpenInParentProcess : 1; 502 503 /** The number of non-options as children of the select */ 504 uint32_t mNonOptionChildren; 505 /** The number of optgroups anywhere under the select */ 506 uint32_t mOptGroupCount; 507 /** 508 * The current selected index for selectedIndex (will be the first selected 509 * index if multiple are selected) 510 */ 511 int32_t mSelectedIndex; 512 /** 513 * The temporary restore state in case we try to restore before parser is 514 * done adding options 515 */ 516 UniquePtr<SelectContentData> mRestoreState; 517 518 /** 519 * The live list of selected options. 520 */ 521 RefPtr<nsContentList> mSelectedOptions; 522 523 /** 524 * The current displayed preview text. 525 */ 526 nsString mPreviewValue; 527 528 private: 529 static void MapAttributesIntoRule(MappedDeclarationsBuilder&); 530 }; 531 532 } // namespace dom 533 } // namespace mozilla 534 535 #endif // mozilla_dom_HTMLSelectElement_h