/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "nsCOMPtr.h" #include "nsCheapSets.h" #include "nsContentUtils.h" #include "nsError.h" #include "nsGenericHTMLElement.h" #include "nsStubMutationObserver.h" class nsContentList; class nsIDOMHTMLOptionElement; class nsIHTMLCollection; class nsListControlFrame; namespace mozilla { class ErrorResult; class EventChainPostVisitor; class EventChainPreVisitor; class SelectContentData; class PresState; namespace dom { class FormData; class HTMLElementOrLong; class HTMLOptionElementOrHTMLOptGroupElement; class HTMLSelectElement; class MOZ_STACK_CLASS SafeOptionListMutation { public: /** * @param aSelect The select element which option list is being mutated. * Can be null. * @param aParent The content object which is being mutated. * @param aKid If not null, a new child element is being inserted to * aParent. Otherwise a child element will be removed. * @param aIndex The index of the content object in the parent. */ SafeOptionListMutation(nsIContent* aSelect, nsIContent* aParent, nsIContent* aKid, uint32_t aIndex, bool aNotify); ~SafeOptionListMutation(); void MutationFailed() { mNeedsRebuild = true; } private: static void* operator new(size_t) noexcept(true) { return nullptr; } static void operator delete(void*, size_t) {} /** The select element which option list is being mutated. */ RefPtr mSelect; /** true if the current mutation is the first one in the stack. */ bool mTopLevelMutation; /** true if it is known that the option list must be recreated. */ bool mNeedsRebuild; /** Whether we should be notifying when we make various method calls on mSelect */ const bool mNotify; /** The selected option at mutation start. */ RefPtr mInitialSelectedOption; /** Option list must be recreated if more than one mutation is detected. */ nsMutationGuard mGuard; }; /** * 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 mOptions->GetNamedItem(aName); } 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*); nsIHTMLCollection* SelectedOptions(); int32_t SelectedIndex() const { return mSelectedIndex; } void SetSelectedIndex(int32_t aIdx) { SetSelectedIndexInternal(aIdx, true); } void GetValue(DOMString& 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); void ShowPicker(ErrorResult& aRv); using nsINode::Remove; // nsINode JSObject* WrapNode(JSContext*, JS::Handle aGivenProto) override; // nsIContent void GetEventTargetParent(EventChainPreVisitor& aVisitor) override; bool IsHTMLFocusable(IsFocusableFlags, bool* aIsFocusable, int32_t* aTabIndex) override; void InsertChildBefore( nsIContent* aKid, nsIContent* aBeforeThis, bool aNotify, ErrorResult& aRv, nsINode* aOldParent = nullptr, MutationEffectOnScript aMutationEffectOnScript = MutationEffectOnScript::DropTrustWorthiness) override; void RemoveChildNode( nsIContent* aKid, bool aNotify, const BatchRemovalState* aState, nsINode* aNewParent = nullptr, MutationEffectOnScript aMutationEffectOnScript = MutationEffectOnScript::DropTrustWorthiness) override; // nsGenericHTMLElement bool IsDisabledForEvents(WidgetEvent* aEvent) override; // nsGenericHTMLFormElement void SaveState() override; bool RestoreState(PresState* aState) override; // Overriden nsIFormControl methods NS_IMETHOD Reset() override; NS_IMETHOD SubmitNamesValues(FormData* aFormData) override; void FieldSetDisabledChanged(bool aNotify) override; /** * To be called when stuff is added under a child of the select--but *before* * they are actually added. * * @param aOptions the content that was added (usually just an option, but * could be an optgroup node with many child options) * @param aParent the parent the options were added to (could be an optgroup) * @param aContentIndex the index where the options are being added within the * parent (if the parent is an optgroup, the index within the optgroup) */ NS_IMETHOD WillAddOptions(nsIContent* aOptions, nsIContent* aParent, int32_t aContentIndex, bool aNotify); /** * To be called when stuff is removed under a child of the select--but * *before* they are actually removed. * * @param aParent the parent the option(s) are being removed from * @param aContentIndex the index of the option(s) within the parent (if the * parent is an optgroup, the index within the optgroup) */ NS_IMETHOD WillRemoveOptions(nsIContent* aParent, int32_t aContentIndex, bool aNotify); /** * 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(); 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) nsIContent* beforeContent = mOptions->GetElementAt(aIndex); return Add(aElement, nsGenericHTMLElement::FromNodeOrNull(beforeContent), aError); } /** Is this a combobox? */ bool IsCombobox() const { return !Multiple() && Size() <= 1; } bool OpenInParentProcess() const { return mIsOpenInParentProcess; } void SetOpenInParentProcess(bool aVal) { mIsOpenInParentProcess = aVal; SetStates(ElementState::OPEN, aVal); } void GetPreviewValue(nsAString& aValue) { aValue = mPreviewValue; } void SetPreviewValue(const nsAString& aValue); void SetAutofillState(const nsAString& aState) { SetFormAutofillState(aState); } void GetAutofillState(nsAString& aState) { GetFormAutofillState(aState); } void SetupShadowTree(); // Returns the text node that has the selected