Bug 1737364 - Part 2: Elements should be barred from constraint validation if it has a datalist element ancestor; r=smaug

Add a new flag, ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR, to indicate that
the element is a HTML datalist element or has a HTML datalist element ancestor.

Per https://html.spec.whatwg.org/multipage/form-elements.html#the-datalist-element:barred-from-constraint-validation

Differential Revision: https://phabricator.services.mozilla.com/D129717
This commit is contained in:
Edgar Chen 2021-11-03 20:07:43 +00:00
parent 6f8feb87ed
commit ad06ef9d0a
11 changed files with 108 additions and 22 deletions

View File

@ -1675,6 +1675,9 @@ nsresult Element::BindToTree(BindContext& aContext, nsINode& aParent) {
if (IsRootOfNativeAnonymousSubtree()) {
aParent.SetMayHaveAnonymousChildren();
}
if (aParent.HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR)) {
SetFlags(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR);
}
// Now set the parent.
mParent = &aParent;
@ -1819,6 +1822,19 @@ bool WillDetachFromShadowOnUnbind(const Element& aElement, bool aNullParent) {
void Element::UnbindFromTree(bool aNullParent) {
HandleShadowDOMRelatedRemovalSteps(aNullParent);
if (HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) &&
!IsHTMLElement(nsGkAtoms::datalist)) {
if (aNullParent) {
UnsetFlags(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR);
} else {
nsIContent* parent = GetParent();
MOZ_ASSERT(parent);
if (!parent->HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR)) {
UnsetFlags(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR);
}
}
}
const bool detachingFromShadow =
WillDetachFromShadowOnUnbind(*this, aNullParent);
// Make sure to only remove from the ID table if our subtree root is actually

View File

@ -168,8 +168,12 @@ enum {
// style of an element is up-to-date, even during the same restyle process.
ELEMENT_HANDLED_SNAPSHOT = ELEMENT_FLAG_BIT(3),
// If this flag is set on an element, that means that it is a HTML datalist
// element or has a HTML datalist element ancestor.
ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR = ELEMENT_FLAG_BIT(4),
// Remaining bits are for subclasses
ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 4
ELEMENT_TYPE_SPECIFIC_BITS_OFFSET = NODE_TYPE_SPECIFIC_BITS_OFFSET + 5
};
#undef ELEMENT_FLAG_BIT

View File

@ -82,9 +82,10 @@ void HTMLButtonElement::SetCustomValidity(const nsAString& aError) {
}
void HTMLButtonElement::UpdateBarredFromConstraintValidation() {
SetBarredFromConstraintValidation(mType == FormControlType::ButtonButton ||
mType == FormControlType::ButtonReset ||
IsDisabled());
SetBarredFromConstraintValidation(
mType == FormControlType::ButtonButton ||
mType == FormControlType::ButtonReset ||
HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled());
}
void HTMLButtonElement::FieldSetDisabledChanged(bool aNotify) {
@ -273,6 +274,8 @@ nsresult HTMLButtonElement::BindToTree(BindContext& aContext,
nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
NS_ENSURE_SUCCESS(rv, rv);
UpdateBarredFromConstraintValidation();
// Update our state; we may now be the default submit element
UpdateState(false);
@ -282,6 +285,8 @@ nsresult HTMLButtonElement::BindToTree(BindContext& aContext,
void HTMLButtonElement::UnbindFromTree(bool aNullParent) {
nsGenericHTMLFormControlElementWithState::UnbindFromTree(aNullParent);
UpdateBarredFromConstraintValidation();
// Update our state; we may no longer be the default submit element
UpdateState(false);
}

View File

@ -11,7 +11,9 @@ NS_IMPL_NS_NEW_HTML_ELEMENT(DataList)
namespace mozilla::dom {
HTMLDataListElement::~HTMLDataListElement() = default;
HTMLDataListElement::~HTMLDataListElement() {
MOZ_ASSERT(HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR));
}
JSObject* HTMLDataListElement::WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {

View File

@ -17,7 +17,9 @@ class HTMLDataListElement final : public nsGenericHTMLElement {
public:
explicit HTMLDataListElement(
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
: nsGenericHTMLElement(std::move(aNodeInfo)) {}
: nsGenericHTMLElement(std::move(aNodeInfo)) {
SetFlags(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR);
}
// nsISupports
NS_DECL_ISUPPORTS_INHERITED

View File

@ -6583,7 +6583,8 @@ void HTMLInputElement::UpdateBarredFromConstraintValidation() {
mType == FormControlType::InputHidden ||
mType == FormControlType::InputButton ||
mType == FormControlType::InputReset ||
HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) || IsDisabled());
HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled());
}
nsresult HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,

View File

@ -1539,7 +1539,8 @@ void HTMLSelectElement::VerifyOptionsArray() {
#endif
void HTMLSelectElement::UpdateBarredFromConstraintValidation() {
SetBarredFromConstraintValidation(IsDisabled());
SetBarredFromConstraintValidation(
HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled());
}
void HTMLSelectElement::FieldSetDisabledChanged(bool aNotify) {

View File

@ -991,7 +991,8 @@ void HTMLTextAreaElement::UpdateValueMissingValidityState() {
void HTMLTextAreaElement::UpdateBarredFromConstraintValidation() {
SetBarredFromConstraintValidation(
HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) || IsDisabled());
HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) || IsDisabled());
}
nsresult HTMLTextAreaElement::GetValidationMessage(

View File

@ -1,13 +1,4 @@
[form-validation-willValidate.html]
[[BUTTON in SUBMIT status\] The willValidate attribute must be false if it has a datalist ancestor]
expected: FAIL
[[select\] The willValidate attribute must be false if it has a datalist ancestor]
expected: FAIL
[[textarea\] The willValidate attribute must be false if it has a datalist ancestor]
expected: FAIL
[[INPUT in COLOR status\] Must be not barred from the constraint validation even if it is readonly]
expected: FAIL

View File

@ -1,7 +1,3 @@
[datalistoptions.html]
[options label/value]
expected: FAIL
[If an element has a datalist element ancestor, it is barred from constraint validation]
expected: FAIL

View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>The constraint validation API Test: element.willValidate</title>
<link rel="author" title="Intel" href="http://www.intel.com/">
<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-cva-willvalidate">
<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-constraint-validation-api">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<datalist></datalist>
<script>
var dl = document.querySelector("datalist");
function runTest(element, name) {
test(function () {
assert_true(element.willValidate, "The willValidate attribute should be true initially.");
dl.appendChild(element);
assert_false(element.willValidate, "The willValidate attribute should be false if element has datalist parent.");
element.remove();
assert_true(element.willValidate, "The willValidate attribute should be true if element has no parent.");
let div = document.createElement("div");
div.appendChild(element);
let foo = document.createElementNS('some-random-namespace', 'foo');
foo.appendChild(div);
dl.appendChild(foo);
assert_false(element.willValidate, "The willValidate attribute should be false if element has datalist ancestor.");
foo.remove();
assert_true(element.willValidate, "The willValidate attribute should be true if element has no datalist ancestor.");
}, name);
}
var testElements = [
{
tag: "input",
types: ["text", "search", "tel", "url", "email", "password", "datetime-local", "date", "month", "week", "time", "color", "file", "submit"]
},
{
tag: "button",
types: ["submit"],
},
{
tag: "select",
types: [],
},
{
tag: "textarea",
types: [],
}
].forEach(function(testData) {
if (testData.types.length > 0) {
testData.types.forEach(function(type) {
let ele = document.createElement(testData.tag);
try {
ele.type = type;
} catch (e) {
//Do nothing, avoid the runtime error breaking the test
}
runTest(ele, `Test ${testData.tag} element with ${type} type`);
});
} else {
runTest(document.createElement(testData.tag), `Test ${testData.tag} element`);
}
});
</script>