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 &lt;select&gt;
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