Bug 1556365 - Implement willValidate attribute of ElementInternals; r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D129329
This commit is contained in:
Edgar Chen 2021-11-04 09:49:32 +00:00
Родитель 6cb8e9938e
Коммит faa3b6cd72
8 изменённых файлов: 140 добавлений и 11 удалений

Просмотреть файл

@ -24,6 +24,7 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(ElementInternals)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ElementInternals)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsIFormControl)
NS_INTERFACE_MAP_ENTRY(nsIConstraintValidation)
NS_INTERFACE_MAP_END
ElementInternals::ElementInternals(HTMLElement* aTarget)
@ -123,6 +124,16 @@ HTMLFormElement* ElementInternals::GetForm(ErrorResult& aRv) const {
return GetForm();
}
// https://html.spec.whatwg.org/#dom-elementinternals-willvalidate
bool ElementInternals::GetWillValidate(ErrorResult& aRv) const {
if (!mTarget || !mTarget->IsFormAssociatedElement()) {
aRv.ThrowNotSupportedError(
"Target element is not a form-associated custom element");
return false;
}
return WillValidate();
}
// https://html.spec.whatwg.org/#dom-elementinternals-labels
already_AddRefed<nsINodeList> ElementInternals::GetLabels(
ErrorResult& aRv) const {
@ -188,6 +199,16 @@ void ElementInternals::UpdateFormOwner() {
}
}
void ElementInternals::UpdateBarredFromConstraintValidation() {
if (mTarget) {
MOZ_ASSERT(mTarget->IsFormAssociatedElement());
SetBarredFromConstraintValidation(
mTarget->HasAttr(nsGkAtoms::readonly) ||
mTarget->HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR) ||
mTarget->IsDisabled());
}
}
void ElementInternals::Unlink() {
if (mForm) {
// Don't notify, since we're being destroyed in any case.

Просмотреть файл

@ -10,6 +10,7 @@
#include "js/TypeDecls.h"
#include "mozilla/dom/ElementInternalsBinding.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIConstraintValidation.h"
#include "nsIFormControl.h"
#include "nsWrapperCache.h"
@ -26,10 +27,13 @@ class HTMLFieldSetElement;
class HTMLFormElement;
class ShadowRoot;
class ElementInternals final : public nsIFormControl, public nsWrapperCache {
class ElementInternals final : public nsIFormControl,
public nsIConstraintValidation,
public nsWrapperCache {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ElementInternals)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(ElementInternals,
nsIFormControl)
explicit ElementInternals(HTMLElement* aTarget);
@ -44,6 +48,7 @@ class ElementInternals final : public nsIFormControl, public nsWrapperCache {
const Optional<Nullable<FileOrUSVStringOrFormData>>& aState,
ErrorResult& aRv);
mozilla::dom::HTMLFormElement* GetForm(ErrorResult& aRv) const;
bool GetWillValidate(ErrorResult& aRv) const;
already_AddRefed<nsINodeList> GetLabels(ErrorResult& aRv) const;
// nsIFormControl
@ -62,6 +67,7 @@ class ElementInternals final : public nsIFormControl, public nsWrapperCache {
}
void UpdateFormOwner();
void UpdateBarredFromConstraintValidation();
void Unlink();

Просмотреть файл

@ -25,6 +25,7 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLElement, nsGenericHTMLFormElement)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLElement)
NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIFormControl, GetElementInternals())
NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIConstraintValidation, GetElementInternals())
NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLFormElement)
NS_IMPL_ADDREF_INHERITED(HTMLElement, nsGenericHTMLFormElement)
@ -37,6 +38,20 @@ JSObject* HTMLElement::WrapNode(JSContext* aCx,
return dom::HTMLElement_Binding::Wrap(aCx, this, aGivenProto);
}
nsresult HTMLElement::BindToTree(BindContext& aContext, nsINode& aParent) {
nsresult rv = nsGenericHTMLFormElement::BindToTree(aContext, aParent);
NS_ENSURE_SUCCESS(rv, rv);
UpdateBarredFromConstraintValidation();
return rv;
}
void HTMLElement::UnbindFromTree(bool aNullParent) {
nsGenericHTMLFormElement::UnbindFromTree(aNullParent);
UpdateBarredFromConstraintValidation();
}
void HTMLElement::SetCustomElementDefinition(
CustomElementDefinition* aDefinition) {
nsGenericHTMLFormElement::SetCustomElementDefinition(aDefinition);
@ -161,6 +176,7 @@ void HTMLElement::UpdateFormOwner() {
}
UpdateFieldSet(true);
UpdateDisabledState(true);
UpdateBarredFromConstraintValidation();
}
nsresult HTMLElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
@ -168,8 +184,14 @@ nsresult HTMLElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
const nsAttrValue* aOldValue,
nsIPrincipal* aMaybeScriptedPrincipal,
bool aNotify) {
if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::disabled) {
UpdateDisabledState(aNotify);
if (aNameSpaceID == kNameSpaceID_None &&
(aName == nsGkAtoms::disabled || aName == nsGkAtoms::readonly)) {
if (aName == nsGkAtoms::disabled) {
// This *has* to be called *before* validity state check because
// UpdateBarredFromConstraintValidation depend on our disabled state.
UpdateDisabledState(aNotify);
}
UpdateBarredFromConstraintValidation();
}
return nsGenericHTMLFormElement::AfterSetAttr(
@ -239,6 +261,14 @@ bool HTMLElement::IsFormAssociatedElement() const {
return isFormAssociatedCustomElement;
}
void HTMLElement::FieldSetDisabledChanged(bool aNotify) {
// This *has* to be called *before* UpdateBarredFromConstraintValidation
// because this function depend on our disabled state.
nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify);
UpdateBarredFromConstraintValidation();
}
ElementInternals* HTMLElement::GetElementInternals() const {
CustomElementData* data = GetCustomElementData();
if (!data || !data->IsFormAssociated()) {
@ -251,6 +281,15 @@ ElementInternals* HTMLElement::GetElementInternals() const {
return data->GetElementInternals();
}
void HTMLElement::UpdateBarredFromConstraintValidation() {
CustomElementData* data = GetCustomElementData();
if (data && data->IsFormAssociated()) {
ElementInternals* internals = data->GetElementInternals();
MOZ_ASSERT(internals);
internals->UpdateBarredFromConstraintValidation();
}
}
} // namespace mozilla::dom
// Here, we expand 'NS_IMPL_NS_NEW_HTML_ELEMENT()' by hand.

Просмотреть файл

@ -23,6 +23,10 @@ class HTMLElement final : public nsGenericHTMLFormElement {
// nsINode
nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
// nsIContent
nsresult BindToTree(BindContext&, nsINode& aParent) override;
void UnbindFromTree(bool aNullParent = true) override;
// Element
void SetCustomElementDefinition(
CustomElementDefinition* aDefinition) override;
@ -36,6 +40,7 @@ class HTMLElement final : public nsGenericHTMLFormElement {
// nsGenericHTMLFormElement
bool IsFormAssociatedElement() const override;
void AfterClearForm(bool aUnbindOrDelete) override;
void FieldSetDisabledChanged(bool aNotify) override;
void UpdateFormOwner();
@ -61,6 +66,8 @@ class HTMLElement final : public nsGenericHTMLFormElement {
void UpdateDisabledState(bool aNotify) override;
void UpdateFormOwner(bool aBindToTree, Element* aFormIdElement) override;
void UpdateBarredFromConstraintValidation();
ElementInternals* GetElementInternals() const;
};

Просмотреть файл

@ -20,6 +20,9 @@ interface ElementInternals {
[Pref="dom.webcomponents.formAssociatedCustomElement.enabled", Throws]
readonly attribute HTMLFormElement? form;
[Pref="dom.webcomponents.formAssociatedCustomElement.enabled", Throws]
readonly attribute boolean willValidate;
[Pref="dom.webcomponents.formAssociatedCustomElement.enabled", Throws]
readonly attribute NodeList labels;
};

Просмотреть файл

@ -11,9 +11,6 @@
[Custom control affects validation at the owner form]
expected: FAIL
[willValidate]
expected: FAIL
["anchor" argument of setValidity()]
expected: FAIL

Просмотреть файл

@ -372,7 +372,8 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu
expected: FAIL
[ElementInternals interface: attribute willValidate]
expected: FAIL
expected:
if release_or_beta: FAIL
[OffscreenCanvasRenderingContext2D interface: operation moveTo(unrestricted double, unrestricted double)]
expected: FAIL

Просмотреть файл

@ -16,6 +16,15 @@ class MyControl extends HTMLElement {
}
customElements.define('my-control', MyControl);
class NotFormAssociatedElement extends HTMLElement {
constructor() {
super();
this.internals_ = this.attachInternals();
}
get i() { return this.internals_; }
}
customElements.define('not-form-associated-element', NotFormAssociatedElement);
test(() => {
const control = new MyControl();
assert_true(control.i.willValidate, 'default value is true');
@ -23,18 +32,64 @@ test(() => {
const datalist = document.createElement('datalist');
datalist.appendChild(control);
assert_false(control.i.willValidate, 'false in DATALIST');
datalist.removeChild(control);
assert_true(control.i.willValidate, 'remove from DATALIST');
const fieldset = document.createElement('fieldset');
fieldset.appendChild(control);
assert_true(control.i.willValidate, 'In enabled FIELDSET');
fieldset.disabled = true;
assert_false(control.i.willValidate, 'In disabled FIELDSET');
fieldset.appendChild(control);
assert_false(control.i.willValidate, 'append to disabled FIELDSET');
fieldset.disabled = false;
assert_true(control.i.willValidate, 'FIELDSET becomes enabled');
fieldset.disabled = true;
assert_false(control.i.willValidate, 'FIELDSET becomes disabled');
fieldset.removeChild(control);
assert_true(control.i.willValidate, 'remove from disabled FIELDSET');
control.setAttribute('disabled', '');
assert_false(control.i.willValidate, 'with disabled attribute');
control.removeAttribute('disabled');
assert_true(control.i.willValidate, 'without disabled attribute');
control.setAttribute('readonly', '');
assert_false(control.i.willValidate, 'with readonly attribute');
control.removeAttribute('readonly');
assert_true(control.i.willValidate, 'without readonly attribute');
}, 'willValidate');
test(() => {
const container = document.getElementById("container");
container.innerHTML = '<will-be-defined></will-be-defined>' +
'<will-be-defined disabled></will-be-defined>' +
'<will-be-defined readonly></will-be-defined>' +
'<datalist><will-be-defined></will-be-defined></datalist>' +
'<fieldset disabled><will-be-defined></will-be-defined></fieldset>';
class WillBeDefined extends HTMLElement {
static get formAssociated() { return true; }
constructor() {
super();
this.internals_ = this.attachInternals();
}
get i() { return this.internals_; }
}
customElements.define('will-be-defined', WillBeDefined);
customElements.upgrade(container);
const controls = document.querySelectorAll('will-be-defined');
assert_true(controls[0].i.willValidate, 'default value');
assert_false(controls[1].i.willValidate, 'with disabled attribute');
assert_false(controls[2].i.willValidate, 'with readOnly attribute');
assert_false(controls[3].i.willValidate, 'in datalist');
assert_false(controls[4].i.willValidate, 'in disabled fieldset');
}, 'willValidate after upgrade');
test(() => {
const element = new NotFormAssociatedElement();
assert_throws_dom('NotSupportedError', () => element.i.willValidate);
}, "willValidate should throw NotSupportedError if the target element is not a form-associated custom element");
test(() => {
const control = document.createElement('my-control');
const validity = control.i.validity;