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
This commit is contained in:
Emilio Cobos Álvarez 2023-08-30 09:18:32 +00:00
parent 7855659a73
commit c27a2129fd
25 changed files with 249 additions and 299 deletions

View File

@ -651,9 +651,7 @@ class Element : public FragmentOrElement {
const AttrArray& GetAttrs() const { return mAttrs; }
void SetDefined(bool aSet) {
SetStates(ElementState::DEFINED, aSet);
}
void SetDefined(bool aSet) { SetStates(ElementState::DEFINED, aSet); }
// AccessibilityRole
REFLECT_DOMSTRING_ATTR(Role, role)
@ -728,6 +726,25 @@ class Element : public FragmentOrElement {
already_AddRefed<ShadowRoot> AttachShadowInternal(ShadowRootMode,
ErrorResult& aError);
struct AutoStateChangeNotifier {
AutoStateChangeNotifier(Element& aElement, bool aNotify)
: mElement(aElement), mOldState(aElement.State()), mNotify(aNotify) {}
~AutoStateChangeNotifier() {
if (!mNotify) {
return;
}
ElementState newState = mElement.State();
if (mOldState != newState) {
mElement.NotifyStateChange(mOldState ^ newState);
}
}
private:
Element& mElement;
const ElementState mOldState;
const bool mNotify;
};
public:
MOZ_CAN_RUN_SCRIPT
nsIScrollableFrame* GetScrollFrame(nsIFrame** aStyledFrame = nullptr,

View File

@ -66,7 +66,7 @@ bool Link::ElementHasHref() const {
}
void Link::SetLinkState(State aState, bool aNotify) {
auto old = mElement->State();
Element::AutoStateChangeNotifier notifier(*mElement, aNotify);
switch (aState) {
case State::Visited:
mElement->AddStatesSilently(ElementState::VISITED);
@ -80,9 +80,6 @@ void Link::SetLinkState(State aState, bool aNotify) {
mElement->RemoveStatesSilently(ElementState::VISITED_OR_UNVISITED);
break;
}
if (aNotify) {
mElement->NotifyStateChange(old ^ mElement->State());
}
}
void Link::TriggerLinkUpdate(bool aNotify) {

View File

@ -46,6 +46,8 @@ bitflags! {
const USER_VALID = 1 << 12;
/// <https://drafts.csswg.org/selectors-4/#user-invalid-pseudo>
const USER_INVALID = 1 << 13;
/// All the validity bits at once.
const VALIDITY_STATES = Self::VALID.bits | Self::INVALID.bits | Self::USER_VALID.bits | Self::USER_INVALID.bits;
/// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-broken
const BROKEN = 1 << 14;
/// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-loading
@ -188,7 +190,8 @@ bitflags! {
Self::INRANGE.bits |
Self::OUTOFRANGE.bits |
Self::VISITED.bits |
Self::UNVISITED.bits;
Self::UNVISITED.bits |
Self::VALIDITY_STATES.bits;
const INTRINSIC_STATES = !Self::EXTERNALLY_MANAGED_STATES.bits;
}

View File

@ -191,7 +191,7 @@ void ElementInternals::SetValidity(
SetValidityState(VALIDITY_STATE_STEP_MISMATCH, aFlags.mStepMismatch);
SetValidityState(VALIDITY_STATE_BAD_INPUT, aFlags.mBadInput);
SetValidityState(VALIDITY_STATE_CUSTOM_ERROR, aFlags.mCustomError);
mTarget->UpdateState(true);
mTarget->UpdateValidityElementStates(true);
/**
* 5. Set element's validation message to the empty string if message is not
@ -305,8 +305,6 @@ bool ElementInternals::ReportValidity(ErrorResult& aRv) {
return false;
}
mTarget->UpdateState(true);
RefPtr<CustomEvent> event =
NS_NewDOMCustomEvent(mTarget->OwnerDoc(), nullptr, nullptr);
event->InitCustomEvent(jsapi.cx(), u"MozInvalidForm"_ns,
@ -448,8 +446,6 @@ nsresult ElementInternals::SetAttr(nsAtom* aName, const nsAString& aValue) {
MutationObservers::NotifyARIAAttributeDefaultChanged(mTarget, aName, modType);
mTarget->UpdateState(true);
return rs;
}

View File

@ -76,8 +76,7 @@ NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(
void HTMLButtonElement::SetCustomValidity(const nsAString& aError) {
ConstraintValidation::SetCustomValidity(aError);
UpdateState(true);
UpdateValidityElementStates(true);
}
void HTMLButtonElement::UpdateBarredFromConstraintValidation() {
@ -94,7 +93,7 @@ void HTMLButtonElement::FieldSetDisabledChanged(bool aNotify) {
nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify);
UpdateBarredFromConstraintValidation();
UpdateState(aNotify);
UpdateValidityElementStates(aNotify);
}
NS_IMPL_ELEMENT_CLONE(HTMLButtonElement)
@ -269,9 +268,7 @@ nsresult HTMLButtonElement::BindToTree(BindContext& aContext,
NS_ENSURE_SUCCESS(rv, rv);
UpdateBarredFromConstraintValidation();
// Update our state; we may now be the default submit element
UpdateState(false);
UpdateValidityElementStates(false);
return NS_OK;
}
@ -280,9 +277,7 @@ void HTMLButtonElement::UnbindFromTree(bool aNullParent) {
nsGenericHTMLFormControlElementWithState::UnbindFromTree(aNullParent);
UpdateBarredFromConstraintValidation();
// Update our state; we may no longer be the default submit element
UpdateState(false);
UpdateValidityElementStates(false);
}
NS_IMETHODIMP
@ -358,6 +353,7 @@ void HTMLButtonElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
}
UpdateBarredFromConstraintValidation();
UpdateValidityElementStates(aNotify);
}
}
@ -383,23 +379,20 @@ bool HTMLButtonElement::RestoreState(PresState* aState) {
if (aState && aState->disabledSet() && !aState->disabled()) {
SetDisabled(false, IgnoreErrors());
}
return false;
}
ElementState HTMLButtonElement::IntrinsicState() const {
ElementState state =
nsGenericHTMLFormControlElementWithState::IntrinsicState();
if (IsCandidateForConstraintValidation()) {
void HTMLButtonElement::UpdateValidityElementStates(bool aNotify) {
AutoStateChangeNotifier notifier(*this, aNotify);
RemoveStatesSilently(ElementState::VALIDITY_STATES);
if (!IsCandidateForConstraintValidation()) {
return;
}
if (IsValid()) {
state |= ElementState::VALID | ElementState::USER_VALID;
AddStatesSilently(ElementState::VALID | ElementState::USER_VALID);
} else {
state |= ElementState::INVALID | ElementState::USER_INVALID;
AddStatesSilently(ElementState::INVALID | ElementState::USER_INVALID);
}
}
return state;
}
JSObject* HTMLButtonElement::WrapNode(JSContext* aCx,

View File

@ -64,8 +64,7 @@ class HTMLButtonElement final : public nsGenericHTMLFormControlElementWithState,
void DoneCreatingElement() override;
void UpdateBarredFromConstraintValidation();
// Element
ElementState IntrinsicState() const override;
void UpdateValidityElementStates(bool aNotify) final;
/**
* Called when an attribute is about to be changed
*/

View File

@ -331,24 +331,25 @@ void HTMLElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
UpdateReadOnlyState(aNotify);
}
UpdateBarredFromConstraintValidation();
UpdateValidityElementStates(aNotify);
}
return nsGenericHTMLFormElement::AfterSetAttr(
aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
}
ElementState HTMLElement::IntrinsicState() const {
ElementState state = nsGenericHTMLFormElement::IntrinsicState();
if (ElementInternals* internals = GetElementInternals()) {
if (internals->IsCandidateForConstraintValidation()) {
void HTMLElement::UpdateValidityElementStates(bool aNotify) {
AutoStateChangeNotifier notifier(*this, aNotify);
RemoveStatesSilently(ElementState::VALIDITY_STATES);
ElementInternals* internals = GetElementInternals();
if (!internals || !internals->IsCandidateForConstraintValidation()) {
return;
}
if (internals->IsValid()) {
state |= ElementState::VALID | ElementState::USER_VALID;
AddStatesSilently(ElementState::VALID | ElementState::USER_VALID);
} else {
state |= ElementState::INVALID | ElementState::USER_INVALID;
AddStatesSilently(ElementState::INVALID | ElementState::USER_INVALID);
}
}
}
return state;
}
void HTMLElement::SetFormInternal(HTMLFormElement* aForm, bool aBindToTree) {

View File

@ -49,6 +49,7 @@ class HTMLElement final : public nsGenericHTMLFormElement {
void AfterClearForm(bool aUnbindOrDelete) override;
void FieldSetDisabledChanged(bool aNotify) override;
void SaveState() override;
void UpdateValidityElementStates(bool aNotify) final;
void UpdateFormOwner();
@ -67,7 +68,6 @@ class HTMLElement final : public nsGenericHTMLFormElement {
const nsAttrValue* aValue, const nsAttrValue* aOldValue,
nsIPrincipal* aMaybeScriptedPrincipal,
bool aNotify) override;
ElementState IntrinsicState() const override;
// nsGenericHTMLFormElement
void SetFormInternal(HTMLFormElement* aForm, bool aBindToTree) override;

View File

@ -294,7 +294,10 @@ void HTMLFieldSetElement::UpdateValidity(bool aElementValidity) {
// - or there is one invalid elmement and an element just became invalid.
if (!mInvalidElementsCount ||
(mInvalidElementsCount == 1 && !aElementValidity)) {
UpdateState(true);
AutoStateChangeNotifier notifier(*this, true);
RemoveStatesSilently(ElementState::VALID | ElementState::INVALID);
AddStatesSilently(mInvalidElementsCount ? ElementState::INVALID
: ElementState::VALID);
}
// We should propagate the change to the fieldset parent chain.
@ -303,18 +306,6 @@ void HTMLFieldSetElement::UpdateValidity(bool aElementValidity) {
}
}
ElementState HTMLFieldSetElement::IntrinsicState() const {
ElementState state = nsGenericHTMLFormControlElement::IntrinsicState();
if (mInvalidElementsCount) {
state |= ElementState::INVALID;
} else {
state |= ElementState::VALID;
}
return state;
}
JSObject* HTMLFieldSetElement::WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return HTMLFieldSetElement_Binding::Wrap(aCx, this, aGivenProto);

View File

@ -90,8 +90,6 @@ class HTMLFieldSetElement final : public nsGenericHTMLFormControlElement,
// XPCOM SetCustomValidity is OK for us
ElementState IntrinsicState() const override;
/*
* This method will update the fieldset's validity. This method has to be
* called by fieldset elements whenever their validity state or status
@ -133,7 +131,7 @@ class HTMLFieldSetElement final : public nsGenericHTMLFormControlElement,
* Number of invalid and candidate for constraint validation
* elements in the fieldSet the last time UpdateValidity has been called.
*
* @note Should only be used by UpdateValidity() and IntrinsicState()!
* @note Should only be used by UpdateValidity()
*/
int32_t mInvalidElementsCount;
};

View File

@ -65,6 +65,8 @@
// radio buttons
#include "mozilla/dom/HTMLInputElement.h"
#include "mozilla/dom/HTMLButtonElement.h"
#include "mozilla/dom/HTMLSelectElement.h"
#include "nsIRadioVisitor.h"
#include "RadioNodeList.h"
@ -388,9 +390,6 @@ static void CollectOrphans(nsINode* aRemovalRoot,
nsCOMPtr<nsIFormControl> fc = do_QueryInterface(node);
MOZ_ASSERT(fc);
fc->ClearForm(true, false);
// When a form control loses its form owner, its state can change.
node->UpdateState(true);
#ifdef DEBUG
removed = true;
#endif
@ -1215,7 +1214,6 @@ nsresult HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
// unless it replaces what's in the slot. If it _does_ replace what's in
// the slot, it becomes the default submit if either the default submit is
// what's in the slot or the child is earlier than the default submit.
nsGenericHTMLFormElement* oldDefaultSubmit = mDefaultSubmitElement;
if (!*firstSubmitSlot ||
(!lastElement && nsContentUtils::CompareTreePosition(
aChild, *firstSubmitSlot, this) < 0)) {
@ -1236,13 +1234,6 @@ nsresult HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild,
mDefaultSubmitElement == mFirstSubmitNotInElements ||
!mDefaultSubmitElement,
"What happened here?");
// Notify that the state of the previous default submit element has changed
// if the element which is the default submit element has changed. The new
// default submit element is responsible for its own state update.
if (oldDefaultSubmit && oldDefaultSubmit != mDefaultSubmitElement) {
oldDefaultSubmit->UpdateState(aNotify);
}
}
// If the element is subject to constraint validaton and is invalid, we need
@ -1342,7 +1333,7 @@ nsresult HTMLFormElement::RemoveElement(nsGenericHTMLFormElement* aChild,
// own notifications.
}
// If the element was subject to constraint validaton and is invalid, we need
// If the element was subject to constraint validation and is invalid, we need
// to update our internal counter.
if (aUpdateValidity) {
nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild);
@ -1794,30 +1785,27 @@ bool HTMLFormElement::CheckValidFormSubmission() {
nsAutoScriptBlocker scriptBlocker;
for (uint32_t i = 0, length = mControls->mElements.Length(); i < length;
++i) {
for (nsGenericHTMLFormElement* element : mControls->mElements) {
// Input elements can trigger a form submission and we want to
// update the style in that case.
if (mControls->mElements[i]->IsHTMLElement(nsGkAtoms::input) &&
if (auto* input = HTMLInputElement::FromNode(*element)) {
// We don't use nsContentUtils::IsFocusedContent here, because it
// doesn't really do what we want for number controls: it's true
// for the anonymous textnode inside, but not the number control
// itself. We can use the focus state, though, because that gets
// synced to the number control by the anonymous text control.
mControls->mElements[i]->State().HasState(ElementState::FOCUS)) {
static_cast<HTMLInputElement*>(mControls->mElements[i])
->UpdateValidityUIBits(true);
if (input->State().HasState(ElementState::FOCUS)) {
input->UpdateValidityUIBits(true);
}
mControls->mElements[i]->UpdateState(true);
}
element->UpdateValidityElementStates(true);
}
// Because of backward compatibility, <input type='image'> is not in
// elements but can be invalid.
// TODO: should probably be removed when bug 606491 will be fixed.
for (uint32_t i = 0, length = mControls->mNotInElements.Length();
i < length; ++i) {
mControls->mNotInElements[i]->UpdateState(true);
for (nsGenericHTMLFormElement* element : mControls->mNotInElements) {
element->UpdateValidityElementStates(true);
}
}
@ -1861,7 +1849,10 @@ void HTMLFormElement::UpdateValidity(bool aElementValidity) {
return;
}
UpdateState(true);
AutoStateChangeNotifier notifier(*this, true);
RemoveStatesSilently(ElementState::VALID | ElementState::INVALID);
AddStatesSilently(mInvalidElementsCount ? ElementState::INVALID
: ElementState::VALID);
}
int32_t HTMLFormElement::IndexOfContent(nsIContent* aContent) {
@ -1922,18 +1913,6 @@ void HTMLFormElement::SetValueMissingState(const nsAString& aName,
RadioGroupManager::SetValueMissingState(aName, aValue);
}
ElementState HTMLFormElement::IntrinsicState() const {
ElementState state = nsGenericHTMLElement::IntrinsicState();
if (mInvalidElementsCount) {
state |= ElementState::INVALID;
} else {
state |= ElementState::VALID;
}
return state;
}
void HTMLFormElement::Clear() {
for (int32_t i = mImageElements.Length() - 1; i >= 0; i--) {
mImageElements[i]->ClearForm(false);

View File

@ -80,8 +80,6 @@ class HTMLFormElement final : public nsGenericHTMLElement,
bool GetValueMissingState(const nsAString& aName) const override;
void SetValueMissingState(const nsAString& aName, bool aValue) override;
ElementState IntrinsicState() const override;
// EventTarget
void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;

View File

@ -1214,6 +1214,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
nsIPrincipal* aSubjectPrincipal,
bool aNotify) {
if (aNameSpaceID == kNameSpaceID_None) {
bool needValidityUpdate = false;
if (aName == nsGkAtoms::src) {
mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
this, aValue ? aValue->GetStringValue() : EmptyString(),
@ -1244,6 +1245,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
// GetStepBase() depends on the `value` attribute if `min` is not present,
// even if the value doesn't change.
UpdateStepMismatchValidityState();
needValidityUpdate = true;
}
// Checked must be set no matter what type of control it is, since
@ -1261,6 +1263,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
DoSetChecked(!!aValue, aNotify, false);
}
}
needValidityUpdate = true;
}
if (aName == nsGkAtoms::type) {
@ -1273,6 +1276,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
}
if (newType != mType) {
HandleTypeChange(newType, aNotify);
needValidityUpdate = true;
}
}
@ -1282,6 +1286,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
mType == FormControlType::InputRadio && (mForm || mDoneCreating)) {
AddedToRadioGroup();
UpdateValueMissingValidityStateForRadio(false);
needValidityUpdate = true;
}
if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
@ -1310,10 +1315,13 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
UpdateBarredFromConstraintValidation();
}
needValidityUpdate = true;
} else if (aName == nsGkAtoms::maxlength) {
UpdateTooLongValidityState();
needValidityUpdate = true;
} else if (aName == nsGkAtoms::minlength) {
UpdateTooShortValidityState();
needValidityUpdate = true;
} else if (aName == nsGkAtoms::pattern) {
// Although pattern attribute only applies to single line text controls,
// we set this flag for all input types to save having to check the type
@ -1323,8 +1331,10 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
if (mDoneCreating) {
UpdatePatternMismatchValidityState();
}
needValidityUpdate = true;
} else if (aName == nsGkAtoms::multiple) {
UpdateTypeMismatchValidityState();
needValidityUpdate = true;
} else if (aName == nsGkAtoms::max) {
UpdateHasRange(aNotify);
mInputType->MinMaxStepAttrChanged();
@ -1333,6 +1343,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
// We don't assert the state of underflow during creation since
// DoneCreatingElement sanitizes.
UpdateRangeOverflowValidityState();
needValidityUpdate = true;
MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
"HTML5 spec does not allow underflow for type=range");
@ -1342,6 +1353,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
// See corresponding @max comment
UpdateRangeUnderflowValidityState();
UpdateStepMismatchValidityState();
needValidityUpdate = true;
MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
"HTML5 spec does not allow underflow for type=range");
@ -1349,6 +1361,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
mInputType->MinMaxStepAttrChanged();
// See corresponding @max comment
UpdateStepMismatchValidityState();
needValidityUpdate = true;
MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
!GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
"HTML5 spec does not allow underflow for type=range");
@ -1361,6 +1374,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
if (mType == FormControlType::InputNumber) {
// The validity of our value may have changed based on the locale.
UpdateValidityState();
needValidityUpdate = true;
}
} else if (aName == nsGkAtoms::autocomplete) {
// Clear the cached @autocomplete attribute and autocompleteInfo state.
@ -1372,6 +1386,7 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
f->PlaceholderChanged(aOldValue, aValue);
}
UpdatePlaceholderShownState();
needValidityUpdate = true;
}
if (CreatesDateTimeWidget()) {
@ -1389,6 +1404,9 @@ void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
}
}
}
if (needValidityUpdate) {
UpdateValidityElementStates(aNotify);
}
}
return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
@ -2250,7 +2268,7 @@ void HTMLInputElement::UpdateValidityState() {
// become valid/invalid. For other validity states, they will be updated when
// .value is actually changed.
UpdateBadInputValidityState();
UpdateState(true);
UpdateValidityElementStates(true);
}
bool HTMLInputElement::MozIsTextField(bool aExcludePassword) {
@ -2771,9 +2789,7 @@ void HTMLInputElement::SetValueChanged(bool aValueChanged) {
mValueChanged = aValueChanged;
UpdateTooLongValidityState();
UpdateTooShortValidityState();
// We need to do this unconditionally because the validity ui bits depend on
// this.
UpdateState(true);
UpdateValidityElementStates(true);
}
void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive) {
@ -2785,7 +2801,7 @@ void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive) {
UpdateTooLongValidityState();
UpdateTooShortValidityState();
if (wasValid != IsValid()) {
UpdateState(true);
UpdateValidityElementStates(true);
}
}
@ -2806,15 +2822,11 @@ void HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged, bool aNotify) {
}
void HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged) {
bool checkedChangedBefore = mCheckedChanged;
mCheckedChanged = aCheckedChanged;
// This method can't be called when we are not authorized to notify
// so we do not need a aNotify parameter.
if (checkedChangedBefore != aCheckedChanged) {
UpdateState(true);
if (mCheckedChanged == aCheckedChanged) {
return;
}
mCheckedChanged = aCheckedChanged;
UpdateValidityElementStates(true);
}
void HTMLInputElement::SetChecked(bool aChecked) {
@ -2986,8 +2998,7 @@ void HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify) {
// UpdateState anyway.
UpdateAllValidityStatesButNotElementState();
UpdateIndeterminateState(aNotify);
// validity state still require UpdateState to be called.
UpdateState(aNotify);
UpdateValidityElementStates(aNotify);
// Notify all radios in the group that value has changed, this is to let
// radios to have the chance to update its states, e.g., :indeterminate.
@ -3461,7 +3472,7 @@ void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
// regardless because we need the UI to update _now_ or the user will
// wonder why the step behavior isn't functioning.
UpdateValidityUIBits(true);
UpdateState(true);
UpdateValidityElementStates(true);
return;
}
}
@ -3627,8 +3638,7 @@ nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
}
UpdateValidityUIBits(aVisitor.mEvent->mMessage == eFocus);
UpdateState(true);
UpdateValidityElementStates(true);
}
nsresult rv = NS_OK;
@ -4276,7 +4286,7 @@ nsresult HTMLInputElement::BindToTree(BindContext& aContext, nsINode& aParent) {
UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date
UpdateState(false);
UpdateValidityElementStates(true);
if (CreatesDateTimeWidget() && IsInComposedDoc()) {
// Construct Shadow Root so web content can be hidden in the DOM.
@ -4325,9 +4335,8 @@ void HTMLInputElement::UnbindFromTree(bool aNullParent) {
UpdateValueMissingValidityState();
// We might be no longer disabled because of parent chain changed.
UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date
UpdateState(false);
UpdateValidityElementStates(false);
}
/**
@ -6062,19 +6071,25 @@ ElementState HTMLInputElement::IntrinsicState() const {
if (mType == FormControlType::InputImage) {
state |= nsImageLoadingContent::ImageState();
}
return state;
}
if (IsCandidateForConstraintValidation()) {
void HTMLInputElement::UpdateValidityElementStates(bool aNotify) {
AutoStateChangeNotifier notifier(*this, aNotify);
RemoveStatesSilently(ElementState::VALIDITY_STATES);
if (!IsCandidateForConstraintValidation()) {
return;
}
ElementState state;
if (IsValid()) {
state |= ElementState::VALID;
} else {
state |= ElementState::INVALID;
if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
(mCanShowInvalidUI && ShouldShowValidityUI())) {
state |= ElementState::USER_INVALID;
}
}
// :-moz-ui-valid applies if all of the following conditions are true:
// 1. The element is not focused, or had either :-moz-ui-valid or
// :-moz-ui-invalid applying before it was focused ;
@ -6087,9 +6102,7 @@ ElementState HTMLInputElement::IntrinsicState() const {
(!state.HasState(ElementState::USER_INVALID) && !mCanShowInvalidUI))) {
state |= ElementState::USER_VALID;
}
}
return state;
AddStatesSilently(state);
}
static nsTArray<OwningFileOrDirectory> RestoreFileContentData(
@ -6540,8 +6553,7 @@ Decimal HTMLInputElement::GetStep() const {
void HTMLInputElement::SetCustomValidity(const nsAString& aError) {
ConstraintValidation::SetCustomValidity(aError);
UpdateState(true);
UpdateValidityElementStates(true);
}
bool HTMLInputElement::IsTooLong() {
@ -6705,7 +6717,7 @@ void HTMLInputElement::UpdateAllValidityStates(bool aNotify) {
bool validBefore = IsValid();
UpdateAllValidityStatesButNotElementState();
if (validBefore != IsValid()) {
UpdateState(aNotify);
UpdateValidityElementStates(aNotify);
}
}
@ -6873,7 +6885,7 @@ void HTMLInputElement::FieldSetDisabledChanged(bool aNotify) {
UpdateValueMissingValidityState();
UpdateBarredFromConstraintValidation();
UpdateState(aNotify);
UpdateValidityElementStates(aNotify);
}
void HTMLInputElement::SetFilePickerFiltersFromAccept(
@ -7115,23 +7127,15 @@ void HTMLInputElement::UpdateValidityUIBits(bool aIsFocused) {
}
void HTMLInputElement::UpdateInRange(bool aNotify) {
AutoStateChangeNotifier notifier(*this, aNotify);
RemoveStatesSilently(ElementState::INRANGE | ElementState::OUTOFRANGE);
if (!mHasRange || !IsCandidateForConstraintValidation()) {
RemoveStates(ElementState::INRANGE | ElementState::OUTOFRANGE, aNotify);
return;
}
bool outOfRange = GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW);
auto oldState = State();
RemoveStatesSilently(ElementState::INRANGE | ElementState::OUTOFRANGE);
AddStatesSilently(outOfRange ? ElementState::OUTOFRANGE
: ElementState::INRANGE);
if (!aNotify) {
return;
}
auto newState = State();
if (oldState != newState) {
NotifyStateChange(oldState ^ newState);
}
}
void HTMLInputElement::UpdateHasRange(bool aNotify) {

View File

@ -331,6 +331,7 @@ class HTMLInputElement final : public TextControlElement,
// as needed. aNotify controls whether the element state update
// needs to notify.
void UpdateAllValidityStates(bool aNotify);
void UpdateValidityElementStates(bool aNotify) final;
MOZ_CAN_RUN_SCRIPT
void MaybeUpdateAllValidityStates(bool aNotify) {
// If you need to add new type which supports validationMessage, you should

View File

@ -56,13 +56,9 @@ void HTMLMeterElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
}
void HTMLMeterElement::UpdateOptimumState(bool aNotify) {
const auto oldState = State();
AutoStateChangeNotifier notifier(*this, aNotify);
RemoveStatesSilently(ElementState::METER_OPTIMUM_STATES);
AddStatesSilently(GetOptimumState());
const auto newState = State();
if (aNotify && oldState != newState) {
NotifyStateChange(oldState ^ newState);
}
}
/*

View File

@ -45,8 +45,6 @@ NS_IMPL_ELEMENT_CLONE(HTMLOutputElement)
void HTMLOutputElement::SetCustomValidity(const nsAString& aError) {
ConstraintValidation::SetCustomValidity(aError);
UpdateState(true);
}
NS_IMETHODIMP
@ -79,21 +77,6 @@ void HTMLOutputElement::DoneAddingChildren(bool aHaveNotified) {
DescendantsChanged();
}
nsresult HTMLOutputElement::BindToTree(BindContext& aContext,
nsINode& aParent) {
nsresult rv = nsGenericHTMLFormControlElement::BindToTree(aContext, aParent);
NS_ENSURE_SUCCESS(rv, rv);
// Unfortunately, we can actually end up having to change our state
// as a result of being bound to a tree even from the parser: we
// might end up a in a novalidate form, and unlike other form
// controls that on its own is enough to make change ui-valid state.
// So just go ahead and update our state now.
UpdateState(false);
return rv;
}
void HTMLOutputElement::GetValue(nsAString& aValue) const {
nsContentUtils::GetNodeTextContent(this, true, aValue);
}

View File

@ -36,14 +36,12 @@ class HTMLOutputElement final : public nsGenericHTMLFormControlElement,
nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
virtual bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
bool ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
const nsAString& aValue,
nsIPrincipal* aMaybeScriptedPrincipal,
nsAttrValue& aResult) override;
virtual void DoneAddingChildren(bool aHaveNotified) override;
virtual nsresult BindToTree(BindContext&, nsINode& aParent) override;
void DoneAddingChildren(bool aHaveNotified) override;
// This function is called when a callback function from nsIMutationObserver
// has to be used to update the defaultValue attribute.
@ -58,8 +56,7 @@ class HTMLOutputElement final : public nsGenericHTMLFormControlElement,
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLOutputElement,
nsGenericHTMLFormControlElement)
virtual JSObject* WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
JSObject* WrapNode(JSContext*, JS::Handle<JSObject*> aGivenProto) override;
// WebIDL
nsDOMTokenList* HtmlFor();

View File

@ -90,7 +90,7 @@ SafeOptionListMutation::~SafeOptionListMutation() {
// update validity here as needed, because by now we know our <option>s
// are where they should be.
mSelect->UpdateValueMissingValidityState();
mSelect->UpdateState(mNotify);
mSelect->UpdateValidityElementStates(mNotify);
}
#ifdef DEBUG
mSelect->VerifyOptionsArray();
@ -161,8 +161,7 @@ NS_IMPL_ELEMENT_CLONE(HTMLSelectElement)
void HTMLSelectElement::SetCustomValidity(const nsAString& aError) {
ConstraintValidation::SetCustomValidity(aError);
UpdateState(true);
UpdateValidityElementStates(true);
}
void HTMLSelectElement::GetAutocomplete(DOMString& aValue) {
@ -344,8 +343,7 @@ nsresult HTMLSelectElement::RemoveOptionsFromList(nsIContent* aOptions,
// Update the validity state in case of we've just removed the last
// option.
UpdateValueMissingValidityState();
UpdateState(aNotify);
UpdateValidityElementStates(aNotify);
}
}
@ -681,7 +679,7 @@ void HTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame,
UpdateSelectedOptions();
UpdateValueMissingValidityState();
UpdateState(aNotify);
UpdateValidityElementStates(aNotify);
}
void HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex, bool aNotify) {
@ -1010,7 +1008,7 @@ bool HTMLSelectElement::SelectSomething(bool aNotify) {
SetSelectedIndexInternal(i, aNotify);
UpdateValueMissingValidityState();
UpdateState(aNotify);
UpdateValidityElementStates(aNotify);
return true;
}
@ -1032,7 +1030,7 @@ nsresult HTMLSelectElement::BindToTree(BindContext& aContext,
UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date
UpdateState(false);
UpdateValidityElementStates(false);
return rv;
}
@ -1046,7 +1044,7 @@ void HTMLSelectElement::UnbindFromTree(bool aNullParent) {
UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date
UpdateState(false);
UpdateValidityElementStates(false);
}
void HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
@ -1087,13 +1085,14 @@ void HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
UpdateValueMissingValidityState();
UpdateBarredFromConstraintValidation();
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::required) {
// This *has* to be called *before* UpdateValueMissingValidityState
// because UpdateValueMissingValidityState depends on our required
// state.
UpdateRequiredState(!!aValue, aNotify);
UpdateValueMissingValidityState();
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::autocomplete) {
// Clear the cached @autocomplete attribute and autocompleteInfo state.
mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
@ -1142,7 +1141,7 @@ void HTMLSelectElement::DoneAddingChildren(bool aHaveNotified) {
UpdateValueMissingValidityState();
// And now make sure we update our content state too
UpdateState(aHaveNotified);
UpdateValidityElementStates(aHaveNotified);
}
mDefaultSelectionSet = true;
@ -1229,18 +1228,20 @@ nsresult HTMLSelectElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
} else if (aVisitor.mEvent->mMessage == eBlur) {
mCanShowInvalidUI = true;
mCanShowValidUI = true;
UpdateState(true);
UpdateValidityElementStates(true);
}
return nsGenericHTMLFormControlElementWithState::PostHandleEvent(aVisitor);
}
ElementState HTMLSelectElement::IntrinsicState() const {
ElementState state =
nsGenericHTMLFormControlElementWithState::IntrinsicState();
void HTMLSelectElement::UpdateValidityElementStates(bool aNotify) {
AutoStateChangeNotifier notifier(*this, aNotify);
RemoveStatesSilently(ElementState::VALIDITY_STATES);
if (!IsCandidateForConstraintValidation()) {
return;
}
if (IsCandidateForConstraintValidation()) {
ElementState state;
if (IsValid()) {
state |= ElementState::VALID;
} else {
@ -1264,9 +1265,8 @@ ElementState HTMLSelectElement::IntrinsicState() const {
(state.HasState(ElementState::USER_INVALID) && !mCanShowInvalidUI))) {
state |= ElementState::USER_VALID;
}
}
return state;
AddStatesSilently(state);
}
void HTMLSelectElement::SaveState() {
@ -1566,7 +1566,7 @@ void HTMLSelectElement::FieldSetDisabledChanged(bool aNotify) {
UpdateValueMissingValidityState();
UpdateBarredFromConstraintValidation();
UpdateState(aNotify);
UpdateValidityElementStates(aNotify);
}
void HTMLSelectElement::SetSelectionChanged(bool aValue, bool aNotify) {
@ -1578,9 +1578,8 @@ void HTMLSelectElement::SetSelectionChanged(bool aValue, bool aNotify) {
bool previousSelectionChangedValue = mSelectionHasChanged;
mSelectionHasChanged = aValue;
if (mSelectionHasChanged != previousSelectionChangedValue) {
UpdateState(aNotify);
UpdateValidityElementStates(aNotify);
}
}

View File

@ -214,8 +214,6 @@ class HTMLSelectElement final : public nsGenericHTMLFormControlElementWithState,
void FieldSetDisabledChanged(bool aNotify) override;
ElementState IntrinsicState() const override;
/**
* To be called when stuff is added under a child of the select--but *before*
* they are actually added.
@ -300,6 +298,7 @@ class HTMLSelectElement final : public nsGenericHTMLFormControlElementWithState,
ValidityStateType aType) override;
void UpdateValueMissingValidityState();
void UpdateValidityElementStates(bool aNotify) final;
/**
* Insert aElement before the node given by aBefore
*/

View File

@ -302,9 +302,7 @@ void HTMLTextAreaElement::SetValueChanged(bool aValueChanged) {
}
UpdateTooLongValidityState();
UpdateTooShortValidityState();
// We need to do this unconditionally because the validity ui bits depend on
// this.
UpdateState(true);
UpdateValidityElementStates(true);
}
void HTMLTextAreaElement::SetLastValueChangeWasInteractive(
@ -317,7 +315,7 @@ void HTMLTextAreaElement::SetLastValueChangeWasInteractive(
UpdateTooLongValidityState();
UpdateTooShortValidityState();
if (wasValid != IsValid()) {
UpdateState(true);
UpdateValidityElementStates(true);
}
}
@ -497,8 +495,7 @@ nsresult HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
mCanShowInvalidUI = true;
mCanShowValidUI = true;
}
UpdateState(true);
UpdateValidityElementStates(true);
}
return NS_OK;
@ -745,11 +742,13 @@ bool HTMLTextAreaElement::RestoreState(PresState* aState) {
return false;
}
ElementState HTMLTextAreaElement::IntrinsicState() const {
ElementState state =
nsGenericHTMLFormControlElementWithState::IntrinsicState();
if (IsCandidateForConstraintValidation()) {
void HTMLTextAreaElement::UpdateValidityElementStates(bool aNotify) {
AutoStateChangeNotifier notifier(*this, aNotify);
RemoveStatesSilently(ElementState::VALIDITY_STATES);
if (!IsCandidateForConstraintValidation()) {
return;
}
ElementState state;
if (IsValid()) {
state |= ElementState::VALID;
} else {
@ -774,8 +773,7 @@ ElementState HTMLTextAreaElement::IntrinsicState() const {
(state.HasState(ElementState::USER_INVALID) && !mCanShowInvalidUI))) {
state |= ElementState::USER_VALID;
}
}
return state;
AddStatesSilently(state);
}
nsresult HTMLTextAreaElement::BindToTree(BindContext& aContext,
@ -795,7 +793,7 @@ nsresult HTMLTextAreaElement::BindToTree(BindContext& aContext,
UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date
UpdateState(false);
UpdateValidityElementStates(false);
return rv;
}
@ -808,7 +806,7 @@ void HTMLTextAreaElement::UnbindFromTree(bool aNullParent) {
UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date
UpdateState(false);
UpdateValidityElementStates(false);
}
void HTMLTextAreaElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
@ -908,13 +906,16 @@ void HTMLTextAreaElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
UpdateBarredFromConstraintValidation();
}
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::autocomplete) {
// Clear the cached @autocomplete attribute state.
mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
} else if (aName == nsGkAtoms::maxlength) {
UpdateTooLongValidityState();
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::minlength) {
UpdateTooShortValidityState();
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::placeholder) {
if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) {
f->PlaceholderChanged(aOldValue, aValue);
@ -957,8 +958,7 @@ bool HTMLTextAreaElement::IsMutable() const { return !IsDisabledOrReadOnly(); }
void HTMLTextAreaElement::SetCustomValidity(const nsAString& aError) {
ConstraintValidation::SetCustomValidity(aError);
UpdateState(true);
UpdateValidityElementStates(true);
}
bool HTMLTextAreaElement::IsTooLong() {
@ -1147,7 +1147,7 @@ void HTMLTextAreaElement::OnValueChanged(ValueChangeKind aKind,
}
if (validBefore != IsValid()) {
UpdateState(true);
UpdateValidityElementStates(true);
}
}
@ -1164,7 +1164,7 @@ void HTMLTextAreaElement::FieldSetDisabledChanged(bool aNotify) {
UpdateValueMissingValidityState();
UpdateBarredFromConstraintValidation();
UpdateState(aNotify);
UpdateValidityElementStates(true);
}
JSObject* HTMLTextAreaElement::WrapNode(JSContext* aCx,

View File

@ -71,8 +71,6 @@ class HTMLTextAreaElement final : public TextControlElement,
void FieldSetDisabledChanged(bool aNotify) override;
ElementState IntrinsicState() const override;
void SetLastValueChangeWasInteractive(bool);
// TextControlElement
@ -393,6 +391,8 @@ class HTMLTextAreaElement final : public TextControlElement,
void GetSelectionRange(uint32_t* aSelectionStart, uint32_t* aSelectionEnd,
ErrorResult& aRv);
void UpdateValidityElementStates(bool aNotify) final;
private:
static void MapAttributesIntoRule(MappedDeclarationsBuilder&);
};

View File

@ -1739,7 +1739,7 @@ void nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm,
UnsetFlags(ADDED_TO_FORM);
SetFormInternal(nullptr, false);
AfterClearForm(aUnbindOrDelete);
UpdateState(true);
UpdateValidityElementStates(true);
}
nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext,
@ -1760,7 +1760,6 @@ nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext,
// Set parent fieldset which should be used for the disabled state.
UpdateFieldSet(false);
return NS_OK;
}
@ -1782,11 +1781,6 @@ void nsGenericHTMLFormElement::UnbindFromTree(bool aNullParent) {
UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
}
}
if (!GetFormInternal()) {
// Our novalidate state might have changed
UpdateState(false);
}
}
// We have to remove the form id observer if there was one.
@ -2100,7 +2094,8 @@ void nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree,
}
if (form != oldForm) {
UpdateState(true);
// ui-valid / invalid depends on the form for some elements
UpdateValidityElementStates(true);
}
}

View File

@ -1015,6 +1015,11 @@ class nsGenericHTMLFormElement : public nsGenericHTMLElement {
*/
already_AddRefed<nsILayoutHistoryState> GetLayoutHistory(bool aRead);
// Form changes (in particular whether our current form has been submitted
// invalidly) affect the user-valid/user-invalid pseudo-classes. Sub-classes
// can override this to react to it.
virtual void UpdateValidityElementStates(bool aNotify) {}
protected:
virtual ~nsGenericHTMLFormElement() = default;

View File

@ -87,9 +87,9 @@ bool nsIConstraintValidation::ReportValidity() {
auto* inputElement = HTMLInputElement::FromNode(element);
if (inputElement && inputElement->State().HasState(ElementState::FOCUS)) {
inputElement->UpdateValidityUIBits(true);
inputElement->UpdateValidityElementStates(true);
}
element->UpdateState(true);
return false;
}
@ -111,8 +111,7 @@ void nsIConstraintValidation::SetValidityState(ValidityStateType aState,
if (HTMLFormElement* form = formCtrl->GetForm()) {
form->UpdateValidity(IsValid());
}
HTMLFieldSetElement* fieldSet = formCtrl->GetFieldSet();
if (fieldSet) {
if (HTMLFieldSetElement* fieldSet = formCtrl->GetFieldSet()) {
fieldSet->UpdateValidity(IsValid());
}
}

View File

@ -35,7 +35,7 @@ bool nsRadioSetValueMissingState::Visit(HTMLInputElement* aRadio) {
aRadio->SetValidityState(
nsIConstraintValidation::VALIDITY_STATE_VALUE_MISSING, mValidity);
aRadio->UpdateState(true);
aRadio->UpdateValidityElementStates(true);
return true;
}
@ -44,6 +44,6 @@ bool nsRadioUpdateStateVisitor::Visit(HTMLInputElement* aRadio) {
return true;
}
aRadio->UpdateIndeterminateState(true);
aRadio->UpdateState(true);
aRadio->UpdateValidityElementStates(true);
return true;
}