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; } const AttrArray& GetAttrs() const { return mAttrs; }
void SetDefined(bool aSet) { void SetDefined(bool aSet) { SetStates(ElementState::DEFINED, aSet); }
SetStates(ElementState::DEFINED, aSet);
}
// AccessibilityRole // AccessibilityRole
REFLECT_DOMSTRING_ATTR(Role, role) REFLECT_DOMSTRING_ATTR(Role, role)
@ -728,6 +726,25 @@ class Element : public FragmentOrElement {
already_AddRefed<ShadowRoot> AttachShadowInternal(ShadowRootMode, already_AddRefed<ShadowRoot> AttachShadowInternal(ShadowRootMode,
ErrorResult& aError); 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: public:
MOZ_CAN_RUN_SCRIPT MOZ_CAN_RUN_SCRIPT
nsIScrollableFrame* GetScrollFrame(nsIFrame** aStyledFrame = nullptr, nsIScrollableFrame* GetScrollFrame(nsIFrame** aStyledFrame = nullptr,

View File

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

View File

@ -46,6 +46,8 @@ bitflags! {
const USER_VALID = 1 << 12; const USER_VALID = 1 << 12;
/// <https://drafts.csswg.org/selectors-4/#user-invalid-pseudo> /// <https://drafts.csswg.org/selectors-4/#user-invalid-pseudo>
const USER_INVALID = 1 << 13; 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 /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-broken
const BROKEN = 1 << 14; const BROKEN = 1 << 14;
/// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-loading /// Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-loading
@ -188,7 +190,8 @@ bitflags! {
Self::INRANGE.bits | Self::INRANGE.bits |
Self::OUTOFRANGE.bits | Self::OUTOFRANGE.bits |
Self::VISITED.bits | Self::VISITED.bits |
Self::UNVISITED.bits; Self::UNVISITED.bits |
Self::VALIDITY_STATES.bits;
const INTRINSIC_STATES = !Self::EXTERNALLY_MANAGED_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_STEP_MISMATCH, aFlags.mStepMismatch);
SetValidityState(VALIDITY_STATE_BAD_INPUT, aFlags.mBadInput); SetValidityState(VALIDITY_STATE_BAD_INPUT, aFlags.mBadInput);
SetValidityState(VALIDITY_STATE_CUSTOM_ERROR, aFlags.mCustomError); 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 * 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; return false;
} }
mTarget->UpdateState(true);
RefPtr<CustomEvent> event = RefPtr<CustomEvent> event =
NS_NewDOMCustomEvent(mTarget->OwnerDoc(), nullptr, nullptr); NS_NewDOMCustomEvent(mTarget->OwnerDoc(), nullptr, nullptr);
event->InitCustomEvent(jsapi.cx(), u"MozInvalidForm"_ns, event->InitCustomEvent(jsapi.cx(), u"MozInvalidForm"_ns,
@ -448,8 +446,6 @@ nsresult ElementInternals::SetAttr(nsAtom* aName, const nsAString& aValue) {
MutationObservers::NotifyARIAAttributeDefaultChanged(mTarget, aName, modType); MutationObservers::NotifyARIAAttributeDefaultChanged(mTarget, aName, modType);
mTarget->UpdateState(true);
return rs; return rs;
} }

View File

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

View File

@ -64,8 +64,7 @@ class HTMLButtonElement final : public nsGenericHTMLFormControlElementWithState,
void DoneCreatingElement() override; void DoneCreatingElement() override;
void UpdateBarredFromConstraintValidation(); void UpdateBarredFromConstraintValidation();
// Element void UpdateValidityElementStates(bool aNotify) final;
ElementState IntrinsicState() const override;
/** /**
* Called when an attribute is about to be changed * 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); UpdateReadOnlyState(aNotify);
} }
UpdateBarredFromConstraintValidation(); UpdateBarredFromConstraintValidation();
UpdateValidityElementStates(aNotify);
} }
return nsGenericHTMLFormElement::AfterSetAttr( return nsGenericHTMLFormElement::AfterSetAttr(
aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify); aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
} }
ElementState HTMLElement::IntrinsicState() const { void HTMLElement::UpdateValidityElementStates(bool aNotify) {
ElementState state = nsGenericHTMLFormElement::IntrinsicState(); AutoStateChangeNotifier notifier(*this, aNotify);
if (ElementInternals* internals = GetElementInternals()) { RemoveStatesSilently(ElementState::VALIDITY_STATES);
if (internals->IsCandidateForConstraintValidation()) { ElementInternals* internals = GetElementInternals();
if (internals->IsValid()) { if (!internals || !internals->IsCandidateForConstraintValidation()) {
state |= ElementState::VALID | ElementState::USER_VALID; return;
} else { }
state |= ElementState::INVALID | ElementState::USER_INVALID; if (internals->IsValid()) {
} AddStatesSilently(ElementState::VALID | ElementState::USER_VALID);
} } else {
AddStatesSilently(ElementState::INVALID | ElementState::USER_INVALID);
} }
return state;
} }
void HTMLElement::SetFormInternal(HTMLFormElement* aForm, bool aBindToTree) { void HTMLElement::SetFormInternal(HTMLFormElement* aForm, bool aBindToTree) {

View File

@ -49,6 +49,7 @@ class HTMLElement final : public nsGenericHTMLFormElement {
void AfterClearForm(bool aUnbindOrDelete) override; void AfterClearForm(bool aUnbindOrDelete) override;
void FieldSetDisabledChanged(bool aNotify) override; void FieldSetDisabledChanged(bool aNotify) override;
void SaveState() override; void SaveState() override;
void UpdateValidityElementStates(bool aNotify) final;
void UpdateFormOwner(); void UpdateFormOwner();
@ -67,7 +68,6 @@ class HTMLElement final : public nsGenericHTMLFormElement {
const nsAttrValue* aValue, const nsAttrValue* aOldValue, const nsAttrValue* aValue, const nsAttrValue* aOldValue,
nsIPrincipal* aMaybeScriptedPrincipal, nsIPrincipal* aMaybeScriptedPrincipal,
bool aNotify) override; bool aNotify) override;
ElementState IntrinsicState() const override;
// nsGenericHTMLFormElement // nsGenericHTMLFormElement
void SetFormInternal(HTMLFormElement* aForm, bool aBindToTree) override; 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. // - or there is one invalid elmement and an element just became invalid.
if (!mInvalidElementsCount || if (!mInvalidElementsCount ||
(mInvalidElementsCount == 1 && !aElementValidity)) { (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. // 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, JSObject* HTMLFieldSetElement::WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) { JS::Handle<JSObject*> aGivenProto) {
return HTMLFieldSetElement_Binding::Wrap(aCx, this, 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 // XPCOM SetCustomValidity is OK for us
ElementState IntrinsicState() const override;
/* /*
* This method will update the fieldset's validity. This method has to be * This method will update the fieldset's validity. This method has to be
* called by fieldset elements whenever their validity state or status * 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 * Number of invalid and candidate for constraint validation
* elements in the fieldSet the last time UpdateValidity has been called. * 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; int32_t mInvalidElementsCount;
}; };

View File

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

View File

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

View File

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

View File

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

View File

@ -90,7 +90,7 @@ SafeOptionListMutation::~SafeOptionListMutation() {
// update validity here as needed, because by now we know our <option>s // update validity here as needed, because by now we know our <option>s
// are where they should be. // are where they should be.
mSelect->UpdateValueMissingValidityState(); mSelect->UpdateValueMissingValidityState();
mSelect->UpdateState(mNotify); mSelect->UpdateValidityElementStates(mNotify);
} }
#ifdef DEBUG #ifdef DEBUG
mSelect->VerifyOptionsArray(); mSelect->VerifyOptionsArray();
@ -161,8 +161,7 @@ NS_IMPL_ELEMENT_CLONE(HTMLSelectElement)
void HTMLSelectElement::SetCustomValidity(const nsAString& aError) { void HTMLSelectElement::SetCustomValidity(const nsAString& aError) {
ConstraintValidation::SetCustomValidity(aError); ConstraintValidation::SetCustomValidity(aError);
UpdateValidityElementStates(true);
UpdateState(true);
} }
void HTMLSelectElement::GetAutocomplete(DOMString& aValue) { 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 // Update the validity state in case of we've just removed the last
// option. // option.
UpdateValueMissingValidityState(); UpdateValueMissingValidityState();
UpdateValidityElementStates(aNotify);
UpdateState(aNotify);
} }
} }
@ -681,7 +679,7 @@ void HTMLSelectElement::OnOptionSelected(nsISelectControlFrame* aSelectFrame,
UpdateSelectedOptions(); UpdateSelectedOptions();
UpdateValueMissingValidityState(); UpdateValueMissingValidityState();
UpdateState(aNotify); UpdateValidityElementStates(aNotify);
} }
void HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex, bool aNotify) { void HTMLSelectElement::FindSelectedIndex(int32_t aStartIndex, bool aNotify) {
@ -1010,7 +1008,7 @@ bool HTMLSelectElement::SelectSomething(bool aNotify) {
SetSelectedIndexInternal(i, aNotify); SetSelectedIndexInternal(i, aNotify);
UpdateValueMissingValidityState(); UpdateValueMissingValidityState();
UpdateState(aNotify); UpdateValidityElementStates(aNotify);
return true; return true;
} }
@ -1032,7 +1030,7 @@ nsresult HTMLSelectElement::BindToTree(BindContext& aContext,
UpdateBarredFromConstraintValidation(); UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date // And now make sure our state is up to date
UpdateState(false); UpdateValidityElementStates(false);
return rv; return rv;
} }
@ -1046,7 +1044,7 @@ void HTMLSelectElement::UnbindFromTree(bool aNullParent) {
UpdateBarredFromConstraintValidation(); UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date // And now make sure our state is up to date
UpdateState(false); UpdateValidityElementStates(false);
} }
void HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, void HTMLSelectElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
@ -1087,13 +1085,14 @@ void HTMLSelectElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
UpdateValueMissingValidityState(); UpdateValueMissingValidityState();
UpdateBarredFromConstraintValidation(); UpdateBarredFromConstraintValidation();
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::required) { } else if (aName == nsGkAtoms::required) {
// This *has* to be called *before* UpdateValueMissingValidityState // This *has* to be called *before* UpdateValueMissingValidityState
// because UpdateValueMissingValidityState depends on our required // because UpdateValueMissingValidityState depends on our required
// state. // state.
UpdateRequiredState(!!aValue, aNotify); UpdateRequiredState(!!aValue, aNotify);
UpdateValueMissingValidityState(); UpdateValueMissingValidityState();
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::autocomplete) { } else if (aName == nsGkAtoms::autocomplete) {
// Clear the cached @autocomplete attribute and autocompleteInfo state. // Clear the cached @autocomplete attribute and autocompleteInfo state.
mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown; mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
@ -1142,7 +1141,7 @@ void HTMLSelectElement::DoneAddingChildren(bool aHaveNotified) {
UpdateValueMissingValidityState(); UpdateValueMissingValidityState();
// And now make sure we update our content state too // And now make sure we update our content state too
UpdateState(aHaveNotified); UpdateValidityElementStates(aHaveNotified);
} }
mDefaultSelectionSet = true; mDefaultSelectionSet = true;
@ -1229,44 +1228,45 @@ nsresult HTMLSelectElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
} else if (aVisitor.mEvent->mMessage == eBlur) { } else if (aVisitor.mEvent->mMessage == eBlur) {
mCanShowInvalidUI = true; mCanShowInvalidUI = true;
mCanShowValidUI = true; mCanShowValidUI = true;
UpdateValidityElementStates(true);
UpdateState(true);
} }
return nsGenericHTMLFormControlElementWithState::PostHandleEvent(aVisitor); return nsGenericHTMLFormControlElementWithState::PostHandleEvent(aVisitor);
} }
ElementState HTMLSelectElement::IntrinsicState() const { void HTMLSelectElement::UpdateValidityElementStates(bool aNotify) {
ElementState state = AutoStateChangeNotifier notifier(*this, aNotify);
nsGenericHTMLFormControlElementWithState::IntrinsicState(); RemoveStatesSilently(ElementState::VALIDITY_STATES);
if (!IsCandidateForConstraintValidation()) {
return;
}
if (IsCandidateForConstraintValidation()) { ElementState state;
if (IsValid()) { if (IsValid()) {
state |= ElementState::VALID; state |= ElementState::VALID;
} else { } else {
state |= ElementState::INVALID; state |= ElementState::INVALID;
if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) || if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
(mCanShowInvalidUI && ShouldShowValidityUI())) { (mCanShowInvalidUI && ShouldShowValidityUI())) {
state |= ElementState::USER_INVALID; state |= ElementState::USER_INVALID;
}
}
// :-moz-ui-valid applies if all the following are true:
// 1. The element is not focused, or had either :-moz-ui-valid or
// :-moz-ui-invalid applying before it was focused ;
// 2. The element is either valid or isn't allowed to have
// :-moz-ui-invalid applying ;
// 3. The element has already been modified or the user tried to submit the
// form owner while invalid.
if (mCanShowValidUI && ShouldShowValidityUI() &&
(IsValid() ||
(state.HasState(ElementState::USER_INVALID) && !mCanShowInvalidUI))) {
state |= ElementState::USER_VALID;
} }
} }
return state; // :-moz-ui-valid applies if all the following are true:
// 1. The element is not focused, or had either :-moz-ui-valid or
// :-moz-ui-invalid applying before it was focused ;
// 2. The element is either valid or isn't allowed to have
// :-moz-ui-invalid applying ;
// 3. The element has already been modified or the user tried to submit the
// form owner while invalid.
if (mCanShowValidUI && ShouldShowValidityUI() &&
(IsValid() ||
(state.HasState(ElementState::USER_INVALID) && !mCanShowInvalidUI))) {
state |= ElementState::USER_VALID;
}
AddStatesSilently(state);
} }
void HTMLSelectElement::SaveState() { void HTMLSelectElement::SaveState() {
@ -1566,7 +1566,7 @@ void HTMLSelectElement::FieldSetDisabledChanged(bool aNotify) {
UpdateValueMissingValidityState(); UpdateValueMissingValidityState();
UpdateBarredFromConstraintValidation(); UpdateBarredFromConstraintValidation();
UpdateState(aNotify); UpdateValidityElementStates(aNotify);
} }
void HTMLSelectElement::SetSelectionChanged(bool aValue, bool aNotify) { void HTMLSelectElement::SetSelectionChanged(bool aValue, bool aNotify) {
@ -1578,9 +1578,8 @@ void HTMLSelectElement::SetSelectionChanged(bool aValue, bool aNotify) {
bool previousSelectionChangedValue = mSelectionHasChanged; bool previousSelectionChangedValue = mSelectionHasChanged;
mSelectionHasChanged = aValue; mSelectionHasChanged = aValue;
if (mSelectionHasChanged != previousSelectionChangedValue) { if (mSelectionHasChanged != previousSelectionChangedValue) {
UpdateState(aNotify); UpdateValidityElementStates(aNotify);
} }
} }

View File

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

View File

@ -302,9 +302,7 @@ void HTMLTextAreaElement::SetValueChanged(bool aValueChanged) {
} }
UpdateTooLongValidityState(); UpdateTooLongValidityState();
UpdateTooShortValidityState(); UpdateTooShortValidityState();
// We need to do this unconditionally because the validity ui bits depend on UpdateValidityElementStates(true);
// this.
UpdateState(true);
} }
void HTMLTextAreaElement::SetLastValueChangeWasInteractive( void HTMLTextAreaElement::SetLastValueChangeWasInteractive(
@ -317,7 +315,7 @@ void HTMLTextAreaElement::SetLastValueChangeWasInteractive(
UpdateTooLongValidityState(); UpdateTooLongValidityState();
UpdateTooShortValidityState(); UpdateTooShortValidityState();
if (wasValid != IsValid()) { if (wasValid != IsValid()) {
UpdateState(true); UpdateValidityElementStates(true);
} }
} }
@ -497,8 +495,7 @@ nsresult HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
mCanShowInvalidUI = true; mCanShowInvalidUI = true;
mCanShowValidUI = true; mCanShowValidUI = true;
} }
UpdateValidityElementStates(true);
UpdateState(true);
} }
return NS_OK; return NS_OK;
@ -745,37 +742,38 @@ bool HTMLTextAreaElement::RestoreState(PresState* aState) {
return false; return false;
} }
ElementState HTMLTextAreaElement::IntrinsicState() const { void HTMLTextAreaElement::UpdateValidityElementStates(bool aNotify) {
ElementState state = AutoStateChangeNotifier notifier(*this, aNotify);
nsGenericHTMLFormControlElementWithState::IntrinsicState(); RemoveStatesSilently(ElementState::VALIDITY_STATES);
if (!IsCandidateForConstraintValidation()) {
if (IsCandidateForConstraintValidation()) { return;
if (IsValid()) { }
state |= ElementState::VALID; ElementState state;
} else { if (IsValid()) {
state |= ElementState::INVALID; state |= ElementState::VALID;
// :-moz-ui-invalid always apply if the element suffers from a custom } else {
// error. state |= ElementState::INVALID;
if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) || // :-moz-ui-invalid always apply if the element suffers from a custom
(mCanShowInvalidUI && ShouldShowValidityUI())) { // error.
state |= ElementState::USER_INVALID; if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
} (mCanShowInvalidUI && ShouldShowValidityUI())) {
} state |= ElementState::USER_INVALID;
// :-moz-ui-valid applies if all the following are true:
// 1. The element is not focused, or had either :-moz-ui-valid or
// :-moz-ui-invalid applying before it was focused ;
// 2. The element is either valid or isn't allowed to have
// :-moz-ui-invalid applying ;
// 3. The element has already been modified or the user tried to submit the
// form owner while invalid.
if (mCanShowValidUI && ShouldShowValidityUI() &&
(IsValid() ||
(state.HasState(ElementState::USER_INVALID) && !mCanShowInvalidUI))) {
state |= ElementState::USER_VALID;
} }
} }
return state;
// :-moz-ui-valid applies if all the following are true:
// 1. The element is not focused, or had either :-moz-ui-valid or
// :-moz-ui-invalid applying before it was focused ;
// 2. The element is either valid or isn't allowed to have
// :-moz-ui-invalid applying ;
// 3. The element has already been modified or the user tried to submit the
// form owner while invalid.
if (mCanShowValidUI && ShouldShowValidityUI() &&
(IsValid() ||
(state.HasState(ElementState::USER_INVALID) && !mCanShowInvalidUI))) {
state |= ElementState::USER_VALID;
}
AddStatesSilently(state);
} }
nsresult HTMLTextAreaElement::BindToTree(BindContext& aContext, nsresult HTMLTextAreaElement::BindToTree(BindContext& aContext,
@ -795,7 +793,7 @@ nsresult HTMLTextAreaElement::BindToTree(BindContext& aContext,
UpdateBarredFromConstraintValidation(); UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date // And now make sure our state is up to date
UpdateState(false); UpdateValidityElementStates(false);
return rv; return rv;
} }
@ -808,7 +806,7 @@ void HTMLTextAreaElement::UnbindFromTree(bool aNullParent) {
UpdateBarredFromConstraintValidation(); UpdateBarredFromConstraintValidation();
// And now make sure our state is up to date // And now make sure our state is up to date
UpdateState(false); UpdateValidityElementStates(false);
} }
void HTMLTextAreaElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName, 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) { if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
UpdateBarredFromConstraintValidation(); UpdateBarredFromConstraintValidation();
} }
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::autocomplete) { } else if (aName == nsGkAtoms::autocomplete) {
// Clear the cached @autocomplete attribute state. // Clear the cached @autocomplete attribute state.
mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown; mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
} else if (aName == nsGkAtoms::maxlength) { } else if (aName == nsGkAtoms::maxlength) {
UpdateTooLongValidityState(); UpdateTooLongValidityState();
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::minlength) { } else if (aName == nsGkAtoms::minlength) {
UpdateTooShortValidityState(); UpdateTooShortValidityState();
UpdateValidityElementStates(aNotify);
} else if (aName == nsGkAtoms::placeholder) { } else if (aName == nsGkAtoms::placeholder) {
if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) { if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) {
f->PlaceholderChanged(aOldValue, aValue); f->PlaceholderChanged(aOldValue, aValue);
@ -957,8 +958,7 @@ bool HTMLTextAreaElement::IsMutable() const { return !IsDisabledOrReadOnly(); }
void HTMLTextAreaElement::SetCustomValidity(const nsAString& aError) { void HTMLTextAreaElement::SetCustomValidity(const nsAString& aError) {
ConstraintValidation::SetCustomValidity(aError); ConstraintValidation::SetCustomValidity(aError);
UpdateValidityElementStates(true);
UpdateState(true);
} }
bool HTMLTextAreaElement::IsTooLong() { bool HTMLTextAreaElement::IsTooLong() {
@ -1147,7 +1147,7 @@ void HTMLTextAreaElement::OnValueChanged(ValueChangeKind aKind,
} }
if (validBefore != IsValid()) { if (validBefore != IsValid()) {
UpdateState(true); UpdateValidityElementStates(true);
} }
} }
@ -1164,7 +1164,7 @@ void HTMLTextAreaElement::FieldSetDisabledChanged(bool aNotify) {
UpdateValueMissingValidityState(); UpdateValueMissingValidityState();
UpdateBarredFromConstraintValidation(); UpdateBarredFromConstraintValidation();
UpdateState(aNotify); UpdateValidityElementStates(true);
} }
JSObject* HTMLTextAreaElement::WrapNode(JSContext* aCx, JSObject* HTMLTextAreaElement::WrapNode(JSContext* aCx,

View File

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

View File

@ -1739,7 +1739,7 @@ void nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm,
UnsetFlags(ADDED_TO_FORM); UnsetFlags(ADDED_TO_FORM);
SetFormInternal(nullptr, false); SetFormInternal(nullptr, false);
AfterClearForm(aUnbindOrDelete); AfterClearForm(aUnbindOrDelete);
UpdateState(true); UpdateValidityElementStates(true);
} }
nsresult nsGenericHTMLFormElement::BindToTree(BindContext& aContext, 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. // Set parent fieldset which should be used for the disabled state.
UpdateFieldSet(false); UpdateFieldSet(false);
return NS_OK; return NS_OK;
} }
@ -1782,11 +1781,6 @@ void nsGenericHTMLFormElement::UnbindFromTree(bool aNullParent) {
UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT); 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. // We have to remove the form id observer if there was one.
@ -2100,7 +2094,8 @@ void nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree,
} }
if (form != oldForm) { 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); 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: protected:
virtual ~nsGenericHTMLFormElement() = default; virtual ~nsGenericHTMLFormElement() = default;

View File

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

View File

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