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