gecko-dev/dom/html/HTMLSelectElement.h
Emilio Cobos Álvarez c27a2129fd Bug 1850293 - Make validity states non-intrinsic. r=smaug
Add a RAII helper to notify of multiple state changes together for
these.

The UpdateState CustomElementInternals calls that are getting removed
are unnecessary (the state should be up-to-date by then, there's nothing
changing there particularly).

Same for the call in nsGenericHTMLFormElement::UnbindFromTree. ClearForm
already does an state update.

Differential Revision: https://phabricator.services.mozilla.com/D187033
2023-08-30 09:18:32 +00:00

549 lines
19 KiB
C++

/* -*- 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/dom/ConstraintValidation.h"
#include "nsGenericHTMLElement.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/UnionTypes.h"
#include "mozilla/dom/HTMLOptionsCollection.h"
#include "mozilla/EnumSet.h"
#include "nsCheapSets.h"
#include "nsCOMPtr.h"
#include "nsError.h"
#include "mozilla/dom/HTMLFormElement.h"
#include "nsContentUtils.h"
class nsContentList;
class nsIDOMHTMLOptionElement;
class nsIHTMLCollection;
class nsISelectControlFrame;
namespace mozilla {
class ErrorResult;
class EventChainPostVisitor;
class EventChainPreVisitor;
class SelectContentData;
class PresState;
namespace dom {
class FormData;
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<HTMLSelectElement> 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<HTMLOptionElement> mInitialSelectedOption;
/** Option list must be recreated if more than one mutation is detected. */
nsMutationGuard mGuard;
};
/**
* Implementation of &lt;select&gt;
*/
class HTMLSelectElement final : public nsGenericHTMLFormControlElementWithState,
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<OptionFlag>;
using ConstraintValidation::GetValidationMessage;
explicit HTMLSelectElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
FromParser aFromParser = NOT_FROM_PARSER);
NS_IMPL_FROMNODE_HTML_WITH_TAG(HTMLSelectElement, select)
// nsISupports
NS_DECL_ISUPPORTS_INHERITED
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);
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<HTMLElementOrLong>& 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);
using nsINode::Remove;
// nsINode
JSObject* WrapNode(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
// nsIContent
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
MOZ_CAN_RUN_SCRIPT
nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
int32_t* aTabIndex) override;
void InsertChildBefore(nsIContent* aKid, nsIContent* aBeforeThis,
bool aNotify, ErrorResult& aRv) override;
void RemoveChildNode(nsIContent* aKid, bool aNotify) 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(bool aNullParent) 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,
int32_t 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) final;
/**
* 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; }
void GetPreviewValue(nsAString& aValue) { aValue = mPreviewValue; }
void SetPreviewValue(const nsAString& aValue);
protected:
virtual ~HTMLSelectElement() = default;
friend class SafeOptionListMutation;
// Helper Methods
/**
* Check whether the option specified by the index is selected
* @param aIndex the index
* @return whether the option at the index is selected
*/
bool IsOptionSelectedByIndex(int32_t aIndex) const;
/**
* Starting with (and including) aStartIndex, find the first selected index
* and set mSelectedIndex to it.
* @param aStartIndex the index to start with
*/
void FindSelectedIndex(int32_t aStartIndex, bool aNotify);
/**
* Select some option if possible (generally the first non-disabled option).
* @return true if something was selected, false otherwise
*/
bool SelectSomething(bool aNotify);
/**
* Call SelectSomething(), but only if nothing is selected
* @see SelectSomething()
* @return true if something was selected, false otherwise
*/
bool CheckSelectSomething(bool aNotify);
/**
* Called to trigger notifications of frames and fixing selected index
*
* @param aSelectFrame the frame for this content (could be null)
* @param aIndex the index that was selected or deselected
* @param aSelected whether the index was selected or deselected
* @param aChangeOptionState if false, don't do anything to the
* HTMLOptionElement at aIndex. If true, change
* its selected state to aSelected.
* @param aNotify whether to notify the style system and such
*/
void OnOptionSelected(nsISelectControlFrame* aSelectFrame, int32_t aIndex,
bool aSelected, bool aChangeOptionState, bool aNotify);
/**
* Restore state to a particular state string (representing the options)
* @param aNewSelected the state string to restore to
*/
void RestoreStateTo(const SelectContentData& aNewSelected);
// Adding options
/**
* Insert option(s) into the options[] array and perform notifications
* @param aOptions the option or optgroup being added
* @param aListIndex the index to start adding options into the list at
* @param aDepth the depth of aOptions (1=direct child of select ...)
*/
void InsertOptionsIntoList(nsIContent* aOptions, int32_t aListIndex,
int32_t aDepth, bool aNotify);
/**
* Remove option(s) from the options[] array
* @param aOptions the option or optgroup being added
* @param aListIndex the index to start removing options from the list at
* @param aDepth the depth of aOptions (1=direct child of select ...)
*/
nsresult RemoveOptionsFromList(nsIContent* aOptions, int32_t aListIndex,
int32_t aDepth, bool aNotify);
// nsIConstraintValidation
void UpdateBarredFromConstraintValidation();
bool IsValueMissing() const;
/**
* Get the index of the first option at, under or following the content in
* the select, or length of options[] if none are found
* @param aOptions the content
* @return the index of the first option
*/
int32_t GetOptionIndexAt(nsIContent* aOptions);
/**
* Get the next option following the content in question (not at or under)
* (this could include siblings of the current content or siblings of the
* parent or children of siblings of the parent).
* @param aOptions the content
* @return the index of the next option after the content
*/
int32_t GetOptionIndexAfter(nsIContent* aOptions);
/**
* Get the first option index at or under the content in question.
* @param aOptions the content
* @return the index of the first option at or under the content
*/
int32_t GetFirstOptionIndex(nsIContent* aOptions);
/**
* Get the first option index under the content in question, within the
* range specified.
* @param aOptions the content
* @param aStartIndex the first child to look at
* @param aEndIndex the child *after* the last child to look at
* @return the index of the first option at or under the content
*/
int32_t GetFirstChildOptionIndex(nsIContent* aOptions, int32_t aStartIndex,
int32_t aEndIndex);
/**
* Get the frame as an nsISelectControlFrame (MAY RETURN nullptr)
* @return the select frame, or null
*/
nsISelectControlFrame* GetSelectFrame();
/**
* Helper method for dispatching ContentReset notifications to list box
* frames.
*/
void DispatchContentReset();
/**
* Rebuilds the options array from scratch as a fallback in error cases.
*/
void RebuildOptionsArray(bool aNotify);
#ifdef DEBUG
void VerifyOptionsArray();
#endif
void SetSelectedIndexInternal(int32_t aIndex, bool aNotify);
void SetSelectionChanged(bool aValue, bool aNotify);
/**
* Marks the selectedOptions list as dirty, so that it'll populate itself
* again.
*/
void UpdateSelectedOptions();
/**
* Return whether an element should have a validity UI.
* (with :-moz-ui-invalid and :-moz-ui-valid pseudo-classes).
*
* @return Whether the element should have a validity UI.
*/
bool ShouldShowValidityUI() const {
/**
* Always show the validity UI if the form has already tried to be submitted
* but was invalid.
*
* Otherwise, show the validity UI if the selection has been changed.
*/
if (mForm && mForm->HasEverTriedInvalidSubmit()) {
return true;
}
return mSelectionHasChanged;
}
/** The options[] array */
RefPtr<HTMLOptionsCollection> mOptions;
nsContentUtils::AutocompleteAttrState mAutocompleteAttrState;
nsContentUtils::AutocompleteAttrState mAutocompleteInfoState;
/** false if the parser is in the middle of adding children. */
bool mIsDoneAddingChildren : 1;
/** true if our disabled state has changed from the default **/
bool mDisabledChanged : 1;
/** true if child nodes are being added or removed.
* Used by SafeOptionListMutation.
*/
bool mMutating : 1;
/**
* True if DoneAddingChildren will get called but shouldn't restore state.
*/
bool mInhibitStateRestoration : 1;
/**
* True if the selection has changed since the element's creation.
*/
bool mSelectionHasChanged : 1;
/**
* True if the default selected option has been set.
*/
bool mDefaultSelectionSet : 1;
/**
* True if :-moz-ui-invalid can be shown.
*/
bool mCanShowInvalidUI : 1;
/**
* True if :-moz-ui-valid can be shown.
*/
bool mCanShowValidUI : 1;
/** True if we're open in the parent process */
bool mIsOpenInParentProcess : 1;
/** The number of non-options as children of the select */
uint32_t mNonOptionChildren;
/** The number of optgroups anywhere under the select */
uint32_t mOptGroupCount;
/**
* The current selected index for selectedIndex (will be the first selected
* index if multiple are selected)
*/
int32_t mSelectedIndex;
/**
* The temporary restore state in case we try to restore before parser is
* done adding options
*/
UniquePtr<SelectContentData> mRestoreState;
/**
* The live list of selected options.
*/
RefPtr<nsContentList> mSelectedOptions;
/**
* The current displayed preview text.
*/
nsString mPreviewValue;
private:
static void MapAttributesIntoRule(MappedDeclarationsBuilder&);
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_HTMLSelectElement_h