зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1556365 - Implement willValidate attribute of ElementInternals; r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D129329
This commit is contained in:
Родитель
6cb8e9938e
Коммит
faa3b6cd72
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче