/* 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/. */ #ifndef mozilla_dom_HTMLSelectElement_h #define mozilla_dom_HTMLSelectElement_h #include "mozilla/Attributes.h" #include "mozilla/EnumSet.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/ConstraintValidation.h" #include "mozilla/dom/HTMLFormElement.h" #include "mozilla/dom/HTMLOptionsCollection.h" #include "nsContentUtils.h" #include "nsError.h" #include "nsGenericHTMLElement.h" #include "nsStubMutationObserver.h" class nsIDOMHTMLOptionElement; class nsListControlFrame; namespace mozilla { class ErrorResult; class EventChainPostVisitor; class EventChainPreVisitor; class SelectContentData; class PresState; class WidgetMouseEvent; namespace dom { class ContentList; class FormData; class HTMLButtonElement; class HTMLCollection; class HTMLElementOrLong; class HTMLOptionElementOrHTMLOptGroupElement; class HTMLSelectElement; class HTMLSelectedContentElement; enum class SelectedContentUpdateMode : uint8_t { MicroTask, ScriptRunner }; /** * Implementation of <select> */ class HTMLSelectElement final : public nsGenericHTMLFormControlElementWithState, public nsStubMutationObserver, public ConstraintValidation { public: /** * IsSelected whether to set the option(s) to true or false * * ClearAll whether to clear all other options (for example, if you * are normal-clicking on the current option) * * SetDisabled whether it is permissible to set disabled options * (for JavaScript) * * Notify whether to notify frames and such * * NoReselect no need to select something after an option is * deselected (for reset) * * InsertingOptions if an option has just been inserted some bailouts can't * be taken */ enum class OptionFlag : uint8_t { IsSelected, ClearAll, SetDisabled, Notify, NoReselect, InsertingOptions }; using OptionFlags = EnumSet; using ConstraintValidation::GetValidationMessage; explicit HTMLSelectElement(already_AddRefed aNodeInfo, FromParser aFromParser = NOT_FROM_PARSER); NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLSelectElement, select) // nsISupports NS_DECL_ISUPPORTS_INHERITED // For comboboxes, we need to keep the list up to date when options change. NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED int32_t TabIndexDefault() override; // Element bool IsInteractiveHTMLContent() const override { return true; } // WebIdl HTMLSelectElement void GetAutocomplete(DOMString& aValue); void SetAutocomplete(const nsAString& aValue, ErrorResult& aRv) { SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv); } void GetAutocompleteInfo(AutocompleteInfo& aInfo); // Sets the user interacted flag and fires input/change events if needed. MOZ_CAN_RUN_SCRIPT void UserFinishedInteracting(bool aChanged); bool Disabled() const { return GetBoolAttr(nsGkAtoms::disabled); } void SetDisabled(bool aVal, ErrorResult& aRv) { SetHTMLBoolAttr(nsGkAtoms::disabled, aVal, aRv); } bool Multiple() const { return GetBoolAttr(nsGkAtoms::multiple); } void SetMultiple(bool aVal, ErrorResult& aRv) { SetHTMLBoolAttr(nsGkAtoms::multiple, aVal, aRv); } void GetName(DOMString& aValue) { GetHTMLAttr(nsGkAtoms::name, aValue); } void SetName(const nsAString& aName, ErrorResult& aRv) { SetHTMLAttr(nsGkAtoms::name, aName, aRv); } bool Required() const { return State().HasState(ElementState::REQUIRED); } void SetRequired(bool aVal, ErrorResult& aRv) { SetHTMLBoolAttr(nsGkAtoms::required, aVal, aRv); } uint32_t Size() const { return GetUnsignedIntAttr(nsGkAtoms::size, 0); } void SetSize(uint32_t aSize, ErrorResult& aRv) { SetUnsignedIntAttr(nsGkAtoms::size, aSize, 0, aRv); } void GetType(nsAString& aValue); HTMLOptionsCollection* Options() const { return mOptions; } uint32_t Length() const { return mOptions->Length(); } void SetLength(uint32_t aLength, ErrorResult& aRv); Element* IndexedGetter(uint32_t aIdx, bool& aFound) const { return mOptions->IndexedGetter(aIdx, aFound); } HTMLOptionElement* Item(uint32_t aIdx) const { return mOptions->ItemAsOption(aIdx); } HTMLOptionElement* NamedItem(const nsAString& aName) const { return static_cast( mOptions->NamedItem(aName, /* aDoFlush = */ true)); } void Add(const HTMLOptionElementOrHTMLOptGroupElement& aElement, const Nullable& aBefore, ErrorResult& aRv); void Remove(int32_t aIndex) const; void IndexedSetter(uint32_t aIndex, HTMLOptionElement* aOption, ErrorResult& aRv) { mOptions->IndexedSetter(aIndex, aOption, aRv); } static bool MatchSelectedOptions(Element* aElement, int32_t, nsAtom*, void*); HTMLCollection* SelectedOptions(); int32_t SelectedIndex() const; // During removal handling we might need to ignore some options that are // getting removed. using IgnoredOptionList = Span>; HTMLOptionElement* GetSelectedOption(IgnoredOptionList = {}) const; void SetSelectedIndex(int32_t aIdx); void GetValue(nsAString& aValue) const; void SetValue(const nsAString& aValue); // Override SetCustomValidity so we update our state properly when it's called // via bindings. void SetCustomValidity(const nsAString& aError); MOZ_CAN_RUN_SCRIPT void ShowPicker(ErrorResult& aRv); using nsINode::Remove; // nsINode JSObject* WrapNode(JSContext*, JS::Handle aGivenProto) override; // nsIContent void GetEventTargetParent(EventChainPreVisitor& aVisitor) override; MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override; HTMLOptionElement* GetCurrentOption() const; bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable, int32_t* aTabIndex) override; // nsGenericHTMLElement bool IsDisabledForEvents(WidgetEvent* aEvent) override; // nsGenericHTMLFormElement void SaveState() override; bool RestoreState(PresState* aState) override; // Overriden nsIFormControl methods MOZ_CAN_RUN_SCRIPT NS_IMETHOD Reset() override; NS_IMETHOD SubmitNamesValues(FormData* aFormData) override; void FieldSetDisabledChanged(bool aNotify) override; /** * Checks whether an option is disabled (even if it's part of an optgroup) * * @param aIndex the index of the option to check * @return whether the option is disabled */ NS_IMETHOD IsOptionDisabled(int32_t aIndex, bool* aIsDisabled); bool IsOptionDisabled(HTMLOptionElement* aOption) const; /** * Sets multiple options (or just sets startIndex if select is single) * and handles notifications and cleanup and everything under the sun. * When this method exits, the select will be in a consistent state. i.e. * if you set the last option to false, it will select an option anyway. * * @param aStartIndex the first index to set * @param aEndIndex the last index to set (set same as first index for one * option) * @param aOptionsMask determines whether to set, clear all or disable * options and whether frames are to be notified of such. * @return whether any options were actually changed */ bool SetOptionsSelectedByIndex(int32_t aStartIndex, int32_t aEndIndex, OptionFlags aOptionsMask); /** * Called when an attribute is about to be changed */ nsresult BindToTree(BindContext&, nsINode& aParent) override; void UnbindFromTree(UnbindContext&) override; void BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, bool aNotify) override; void AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, nsIPrincipal* aSubjectPrincipal, bool aNotify) override; void DoneAddingChildren(bool aHaveNotified) override; bool IsDoneAddingChildren() const { return mIsDoneAddingChildren; } bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, const nsAString& aValue, nsIPrincipal* aMaybeScriptedPrincipal, nsAttrValue& aResult) override; nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override; nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute, AttrModType aModType) const override; NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override; nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override; NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( HTMLSelectElement, nsGenericHTMLFormControlElementWithState) HTMLOptionsCollection* GetOptions() { return mOptions; } // ConstraintValidation nsresult GetValidationMessage(nsAString& aValidationMessage, ValidityStateType aType) override; void UpdateValueMissingValidityState(IgnoredOptionList = {}); void UpdateValidityElementStates(bool aNotify); /** * Insert aElement before the node given by aBefore */ void Add(nsGenericHTMLElement& aElement, nsGenericHTMLElement* aBefore, ErrorResult& aError); void Add(nsGenericHTMLElement& aElement, int32_t aIndex, ErrorResult& aError) { // If item index is out of range, insert to last. // (since beforeElement becomes null, it is inserted to last) Element* beforeContent = mOptions->Item(aIndex); return Add(aElement, nsGenericHTMLElement::FromNodeOrNull(beforeContent), aError); } /** Is this a combobox? */ bool IsCombobox() const { return !Multiple() && Size() <= 1; } bool IsBaseSelectAppearance() const; nsGenericHTMLElement* GetPickerElement() const; MOZ_CAN_RUN_SCRIPT void TogglePickerInternal( bool aIsSourceTouchEvent = false); bool OpenInParentProcess() const { return mIsOpenInParentProcess; } void SetOpenInParentProcess(bool aVal) { mIsOpenInParentProcess = aVal; SetStates(ElementState::OPEN, aVal); } void OnPopoverStateChanged(bool aOpen) { SetStates(ElementState::OPEN, aOpen); } void GetPreviewValue(nsAString& aValue) { aValue = mPreviewValue; } void SetPreviewValue(const nsAString& aValue); void SetAutofillState(const nsAString& aState) { SetFormAutofillState(aState); } void GetAutofillState(nsAString& aState) { GetFormAutofillState(aState); } HTMLButtonElement* GetFirstButton() const; void SetupShadowTree(); void GetSlotNameFor(const ShadowRoot&, const nsIContent&, nsAString&) const override; void OnChildBeforeSlotted(ShadowRoot&, nsIContent&) override; void OnChildUnslotted(ShadowRoot&, nsIContent&) override; // Returns the text node that has the selected