Commit f8a9e418 authored by bartekn@chromium.org's avatar bartekn@chromium.org

Implement :valid and :invalid pseudoclass for <form> (resubmitting)

BUG=360466
TEST=added form-pseudo-valid-style.html layout test

Review URL: https://codereview.chromium.org/646543004

git-svn-id: svn://svn.chromium.org/blink/trunk@183729 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent cb63eab7
Check if :valid/:invalid CSS pseudo selectors are lively applied for forms
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
Removing and adding required text inputs and modifying ther value by a DOM tree mutation:
PASS backgroundOf(form1) is invalidColor
PASS backgroundOf(sub1) is subInvalidColor
PASS form1.removeChild(input1); backgroundOf(form1) is validColor
PASS backgroundOf(sub1) is subValidColor
PASS form1.appendChild(input1); backgroundOf(form1) is invalidColor
PASS backgroundOf(sub1) is subInvalidColor
PASS input1.setAttribute("value", "a"); backgroundOf(form1) is validColor
PASS backgroundOf(sub1) is subValidColor
PASS input2.setAttribute("value", ""); backgroundOf(form1) is invalidColor
PASS backgroundOf(sub1) is subInvalidColor
Adding a required text input that is not a direct child of the form:
PASS backgroundOf(form1) is validColor
PASS div1.appendChild(input1); backgroundOf(form1) is invalidColor
Render multiple forms and move an invalid input from one to another:
PASS backgroundOf($("form1")) is invalidColor
PASS backgroundOf($("form2")) is validColor
PASS backgroundOf($("form3")) is validColor
PASS backgroundOf($("form1")) is validColor
PASS backgroundOf($("form3")) is invalidColor
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<html>
<head>
<script src="../../resources/js-test.js"></script>
<style>
:invalid { background: rgb(255, 0, 0); }
:valid { background: rgb(0, 255, 0); }
form:invalid input[type=submit] { background-color: rgb(127, 0, 0); }
form:valid input[type=submit] { background-color: rgb(0, 127, 0); }
</style>
</head>
<body>
<script>
description('Check if :valid/:invalid CSS pseudo selectors are lively applied for forms');
function $(id) {
return document.getElementById(id);
}
function backgroundOf(element) {
return document.defaultView.getComputedStyle(element, null).getPropertyValue('background-color');
}
var invalidColor = 'rgb(255, 0, 0)';
var validColor = 'rgb(0, 255, 0)';
var subInvalidColor = 'rgb(127, 0, 0)';
var subValidColor = 'rgb(0, 127, 0)';
var parent = document.createElement('div');
document.body.appendChild(parent);
debug('Removing and adding required text inputs and modifying ther value by a DOM tree mutation:');
parent.innerHTML = '<form id=form1><input type=text id=input1 required><input type=text id=input2 required value="a"><input type=submit id=sub1></form>';
var form1 = $('form1');
var input1 = $('input1');
var input2 = $('input2');
var sub1 = $('sub1');
shouldBe('backgroundOf(form1)', 'invalidColor');
shouldBe('backgroundOf(sub1)', 'subInvalidColor');
shouldBe('form1.removeChild(input1); backgroundOf(form1)', 'validColor');
shouldBe('backgroundOf(sub1)', 'subValidColor');
shouldBe('form1.appendChild(input1); backgroundOf(form1)', 'invalidColor');
shouldBe('backgroundOf(sub1)', 'subInvalidColor');
shouldBe('input1.setAttribute("value", "a"); backgroundOf(form1)', 'validColor');
shouldBe('backgroundOf(sub1)', 'subValidColor');
shouldBe('input2.setAttribute("value", ""); backgroundOf(form1)', 'invalidColor');
shouldBe('backgroundOf(sub1)', 'subInvalidColor');
debug('')
debug('Adding a required text input that is not a direct child of the form:');
parent.innerHTML = '<form id=form1></form>';
var form1 = $('form1');
shouldBe('backgroundOf(form1)', 'validColor');
var div1 = document.createElement('div');
var input1 = document.createElement('input');
input1.setAttribute('type', 'text');
input1.setAttribute('required', '');
form1.appendChild(div1);
shouldBe('div1.appendChild(input1); backgroundOf(form1)', 'invalidColor');
debug('');
debug('Render multiple forms and move an invalid input from one to another:');
parent.innerHTML = '<form id=form1><input type=text id=input1 required><input type=text id=input2 required value="a"></form>'
+ '<form id=form2><input type=text id=input3><input type=text id=input4 required value="a"></form>'
+ '<form id=form3></form>';
shouldBe('backgroundOf($("form1"))', 'invalidColor');
shouldBe('backgroundOf($("form2"))', 'validColor');
shouldBe('backgroundOf($("form3"))', 'validColor');
var input1 = $('input1');
var form1 = $('form1');
var form3 = $('form3');
input1.setAttribute("form", "form3");
shouldBe('backgroundOf($("form1"))', 'validColor');
shouldBe('backgroundOf($("form3"))', 'invalidColor');
debug('');
parent.innerHTML = '';
</script>
</body>
</html>
......@@ -838,10 +838,10 @@ bool SelectorChecker::checkPseudoClass(const SelectorCheckingContext& context, c
return element.isRequiredFormControl();
case CSSSelector::PseudoValid:
element.document().setContainsValidityStyleRules();
return element.willValidate() && element.isValidFormControlElement();
return element.matchesValidityPseudoClasses() && element.isValidElement();
case CSSSelector::PseudoInvalid:
element.document().setContainsValidityStyleRules();
return element.willValidate() && !element.isValidFormControlElement();
return element.matchesValidityPseudoClasses() && !element.isValidElement();
case CSSSelector::PseudoChecked:
{
if (isHTMLInputElement(element)) {
......
......@@ -84,7 +84,7 @@ bool SharedStyleFinder::canShareStyleWithControl(Element& candidate) const
if (willValidate != element().willValidate())
return false;
if (willValidate && (candidate.isValidFormControlElement() != element().isValidFormControlElement()))
if (willValidate && (candidate.isValidElement() != element().isValidElement()))
return false;
if (candidate.isInRange() != element().isInRange())
......@@ -265,6 +265,11 @@ bool SharedStyleFinder::canShareStyleWithElement(Element& candidate) const
return false;
}
if (document().containsValidityStyleRules()) {
if (candidate.isValidElement() != element().isValidElement())
return false;
}
return true;
}
......
......@@ -409,6 +409,7 @@ public:
virtual bool matchesReadOnlyPseudoClass() const { return false; }
virtual bool matchesReadWritePseudoClass() const { return false; }
virtual bool matchesValidityPseudoClasses() const { return false; }
bool matches(const String& selectors, ExceptionState&);
virtual bool shouldAppearIndeterminate() const { return false; }
......@@ -429,7 +430,7 @@ public:
virtual bool isRequiredFormControl() const { return false; }
virtual bool isDefaultButtonForForm() const { return false; }
virtual bool willValidate() const { return false; }
virtual bool isValidFormControlElement() { return false; }
virtual bool isValidElement() { return false; }
virtual bool isInRange() const { return false; }
virtual bool isOutOfRange() const { return false; }
virtual bool isClearButtonElement() const { return false; }
......
......@@ -266,6 +266,25 @@ void HTMLFormControlElement::removedFrom(ContainerNode* insertionPoint)
FormAssociatedElement::removedFrom(insertionPoint);
}
void HTMLFormControlElement::willChangeForm()
{
formOwnerSetNeedsValidityCheck();
FormAssociatedElement::willChangeForm();
}
void HTMLFormControlElement::didChangeForm()
{
formOwnerSetNeedsValidityCheck();
FormAssociatedElement::didChangeForm();
}
void HTMLFormControlElement::formOwnerSetNeedsValidityCheck()
{
HTMLFormElement* form = formOwner();
if (form)
form->setNeedsValidityCheck();
}
void HTMLFormControlElement::setChangedSinceLastFormControlChangeEvent(bool changed)
{
m_wasChangedSinceLastFormControlChangeEvent = changed;
......@@ -469,7 +488,7 @@ ValidationMessageClient* HTMLFormControlElement::validationMessageClient() const
bool HTMLFormControlElement::checkValidity(WillBeHeapVector<RefPtrWillBeMember<FormAssociatedElement> >* unhandledInvalidControls)
{
if (!willValidate() || isValidFormControlElement())
if (!willValidate() || isValidElement())
return true;
// An event handler can deref this object.
RefPtrWillBeRawPtr<HTMLFormControlElement> protector(this);
......@@ -480,7 +499,12 @@ bool HTMLFormControlElement::checkValidity(WillBeHeapVector<RefPtrWillBeMember<F
return false;
}
bool HTMLFormControlElement::isValidFormControlElement()
bool HTMLFormControlElement::matchesValidityPseudoClasses() const
{
return willValidate();
}
bool HTMLFormControlElement::isValidElement()
{
// If the following assertion fails, setNeedsValidityCheck() is not called
// correctly when something which changes validity is updated.
......@@ -492,6 +516,7 @@ void HTMLFormControlElement::setNeedsValidityCheck()
{
bool newIsValid = valid();
if (willValidate() && newIsValid != m_isValid) {
formOwnerSetNeedsValidityCheck();
// Update style for pseudo classes such as :valid :invalid.
setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::createWithExtraData(StyleChangeReason::PseudoClass, StyleChangeExtraData::Invalid));
}
......
......@@ -85,6 +85,8 @@ public:
virtual void setActivatedSubmit(bool) { }
virtual bool willValidate() const override;
virtual bool matchesValidityPseudoClasses() const override;
void updateVisibleValidationMessage();
void hideVisibleValidationMessage();
bool checkValidity(WillBeHeapVector<RefPtrWillBeMember<FormAssociatedElement> >* unhandledInvalidControls = 0);
......@@ -121,6 +123,8 @@ protected:
virtual void attach(const AttachContext& = AttachContext()) override;
virtual InsertionNotificationRequest insertedInto(ContainerNode*) override;
virtual void removedFrom(ContainerNode*) override;
virtual void willChangeForm() override;
virtual void didChangeForm() override;
virtual void didMoveToNewDocument(Document& oldDocument) override;
virtual bool supportsFocus() const override;
......@@ -152,12 +156,15 @@ private:
virtual short tabIndex() const override final;
virtual bool isDefaultButtonForForm() const override final;
virtual bool isValidFormControlElement() override final;
virtual bool isValidElement() override final;
void updateAncestorDisabledState() const;
bool isValidationMessageVisible() const;
ValidationMessageClient* validationMessageClient() const;
// Requests validity recalc for the form owner, if one exists.
void formOwnerSetNeedsValidityCheck();
bool m_disabled : 1;
bool m_isAutofilled : 1;
bool m_isReadOnly : 1;
......
......@@ -109,6 +109,16 @@ void HTMLFormElement::trace(Visitor* visitor)
HTMLElement::trace(visitor);
}
bool HTMLFormElement::matchesValidityPseudoClasses() const
{
return true;
}
bool HTMLFormElement::isValidElement()
{
return checkValidity();
}
bool HTMLFormElement::rendererIsNeeded(const RenderStyle& style)
{
if (!m_wasDemoted)
......@@ -713,6 +723,14 @@ HTMLFormControlElement* HTMLFormElement::defaultButton() const
return 0;
}
void HTMLFormElement::setNeedsValidityCheck()
{
// For now unconditionally order style recalculation, which triggers
// validity recalculation. In the near future, implement validity cache and
// recalculate style only if it changed.
setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::createWithExtraData(StyleChangeReason::PseudoClass, StyleChangeExtraData::Invalid));
}
bool HTMLFormElement::checkValidity()
{
return !checkInvalidControlsAndCollectUnhandled(0);
......
......@@ -47,6 +47,10 @@ public:
virtual ~HTMLFormElement();
virtual void trace(Visitor*) override;
virtual bool matchesValidityPseudoClasses() const override final;
virtual bool isValidElement() override final;
void setNeedsValidityCheck();
PassRefPtrWillBeRawPtr<HTMLFormControlsCollection> elements();
void getNamedElements(const AtomicString&, WillBeHeapVector<RefPtrWillBeMember<Element> >&);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment