1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/BasicEvents.h"
8 #include "mozilla/EventDispatcher.h"
9 #include "mozilla/Maybe.h"
10 #include "mozilla/StaticPrefs_dom.h"
11 #include "mozilla/dom/CustomElementRegistry.h"
12 #include "mozilla/dom/HTMLFieldSetElement.h"
13 #include "mozilla/dom/HTMLFieldSetElementBinding.h"
14 #include "nsContentList.h"
15 #include "nsQueryObject.h"
17 NS_IMPL_NS_NEW_HTML_ELEMENT(FieldSet
)
19 namespace mozilla::dom
{
21 HTMLFieldSetElement::HTMLFieldSetElement(
22 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
23 : nsGenericHTMLFormControlElement(std::move(aNodeInfo
),
24 FormControlType::Fieldset
),
26 mFirstLegend(nullptr),
27 mInvalidElementsCount(0) {
28 // <fieldset> is always barred from constraint validation.
29 SetBarredFromConstraintValidation(true);
31 // We start out enabled and valid.
32 AddStatesSilently(ElementState::ENABLED
| ElementState::VALID
);
35 HTMLFieldSetElement::~HTMLFieldSetElement() {
36 uint32_t length
= mDependentElements
.Length();
37 for (uint32_t i
= 0; i
< length
; ++i
) {
38 mDependentElements
[i
]->ForgetFieldSet(this);
42 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement
,
43 nsGenericHTMLFormControlElement
, mValidity
,
46 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement
,
47 nsGenericHTMLFormControlElement
,
48 nsIConstraintValidation
)
50 NS_IMPL_ELEMENT_CLONE(HTMLFieldSetElement
)
52 bool HTMLFieldSetElement::IsDisabledForEvents(WidgetEvent
* aEvent
) {
53 if (StaticPrefs::dom_forms_fieldset_disable_only_descendants_enabled()) {
56 return IsElementDisabledForEvents(aEvent
, nullptr);
60 void HTMLFieldSetElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
61 // Do not process any DOM events if the element is disabled.
62 aVisitor
.mCanHandle
= false;
63 if (IsDisabledForEvents(aVisitor
.mEvent
)) {
67 nsGenericHTMLFormControlElement::GetEventTargetParent(aVisitor
);
70 void HTMLFieldSetElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
71 const nsAttrValue
* aValue
,
72 const nsAttrValue
* aOldValue
,
73 nsIPrincipal
* aSubjectPrincipal
,
75 if (aNameSpaceID
== kNameSpaceID_None
&& aName
== nsGkAtoms::disabled
) {
76 // This *has* to be called *before* calling FieldSetDisabledChanged on our
77 // controls, as they may depend on our disabled state.
78 UpdateDisabledState(aNotify
);
81 return nsGenericHTMLFormControlElement::AfterSetAttr(
82 aNameSpaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
85 void HTMLFieldSetElement::GetType(nsAString
& aType
) const {
86 aType
.AssignLiteral("fieldset");
90 bool HTMLFieldSetElement::MatchListedElements(Element
* aElement
,
92 nsAtom
* aAtom
, void* aData
) {
93 nsCOMPtr
<nsIFormControl
> formControl
= do_QueryInterface(aElement
);
97 nsIHTMLCollection
* HTMLFieldSetElement::Elements() {
100 new nsContentList(this, MatchListedElements
, nullptr, nullptr, true);
108 nsresult
HTMLFieldSetElement::Reset() { return NS_OK
; }
110 void HTMLFieldSetElement::InsertChildBefore(nsIContent
* aChild
,
111 nsIContent
* aBeforeThis
,
112 bool aNotify
, ErrorResult
& aRv
) {
113 bool firstLegendHasChanged
= false;
115 if (aChild
->IsHTMLElement(nsGkAtoms::legend
)) {
117 mFirstLegend
= aChild
;
118 // We do not want to notify the first time mFirstElement is set.
120 // If mFirstLegend is before aIndex, we do not change it.
121 // Otherwise, mFirstLegend is now aChild.
122 const Maybe
<uint32_t> indexOfRef
=
123 aBeforeThis
? ComputeIndexOf(aBeforeThis
) : Some(GetChildCount());
124 const Maybe
<uint32_t> indexOfFirstLegend
= ComputeIndexOf(mFirstLegend
);
125 if ((indexOfRef
.isSome() && indexOfFirstLegend
.isSome() &&
126 *indexOfRef
<= *indexOfFirstLegend
) ||
127 // XXX Keep the odd traditional behavior for now.
128 indexOfRef
.isNothing()) {
129 mFirstLegend
= aChild
;
130 firstLegendHasChanged
= true;
135 nsGenericHTMLFormControlElement::InsertChildBefore(aChild
, aBeforeThis
,
141 if (firstLegendHasChanged
) {
142 NotifyElementsForFirstLegendChange(aNotify
);
146 void HTMLFieldSetElement::RemoveChildNode(nsIContent
* aKid
, bool aNotify
) {
147 bool firstLegendHasChanged
= false;
149 if (mFirstLegend
&& aKid
== mFirstLegend
) {
150 // If we are removing the first legend we have to found another one.
151 nsIContent
* child
= mFirstLegend
->GetNextSibling();
152 mFirstLegend
= nullptr;
153 firstLegendHasChanged
= true;
155 for (; child
; child
= child
->GetNextSibling()) {
156 if (child
->IsHTMLElement(nsGkAtoms::legend
)) {
157 mFirstLegend
= child
;
163 nsGenericHTMLFormControlElement::RemoveChildNode(aKid
, aNotify
);
165 if (firstLegendHasChanged
) {
166 NotifyElementsForFirstLegendChange(aNotify
);
170 void HTMLFieldSetElement::AddElement(nsGenericHTMLFormElement
* aElement
) {
171 mDependentElements
.AppendElement(aElement
);
173 // If the element that we are adding aElement is a fieldset, then all the
174 // invalid elements in aElement are also invalid elements of this.
175 HTMLFieldSetElement
* fieldSet
= FromNode(aElement
);
177 for (int32_t i
= 0; i
< fieldSet
->mInvalidElementsCount
; i
++) {
178 UpdateValidity(false);
183 // If the element is a form-associated custom element, adding element might be
184 // caused by FACE upgrade which won't trigger mutation observer, so mark
185 // mElements dirty manually here.
186 CustomElementData
* data
= aElement
->GetCustomElementData();
187 if (data
&& data
->IsFormAssociated() && mElements
) {
188 mElements
->SetDirty();
191 // We need to update the validity of the fieldset.
192 nsCOMPtr
<nsIConstraintValidation
> cvElmt
= do_QueryObject(aElement
);
193 if (cvElmt
&& cvElmt
->IsCandidateForConstraintValidation() &&
194 !cvElmt
->IsValid()) {
195 UpdateValidity(false);
199 int32_t debugInvalidElementsCount
= 0;
200 for (uint32_t i
= 0; i
< mDependentElements
.Length(); i
++) {
201 HTMLFieldSetElement
* fieldSet
= FromNode(mDependentElements
[i
]);
203 debugInvalidElementsCount
+= fieldSet
->mInvalidElementsCount
;
206 nsCOMPtr
<nsIConstraintValidation
> cvElmt
=
207 do_QueryObject(mDependentElements
[i
]);
208 if (cvElmt
&& cvElmt
->IsCandidateForConstraintValidation() &&
209 !(cvElmt
->IsValid())) {
210 debugInvalidElementsCount
+= 1;
213 MOZ_ASSERT(debugInvalidElementsCount
== mInvalidElementsCount
);
217 void HTMLFieldSetElement::RemoveElement(nsGenericHTMLFormElement
* aElement
) {
218 mDependentElements
.RemoveElement(aElement
);
220 // If the element that we are removing aElement is a fieldset, then all the
221 // invalid elements in aElement are also removed from this.
222 HTMLFieldSetElement
* fieldSet
= FromNode(aElement
);
224 for (int32_t i
= 0; i
< fieldSet
->mInvalidElementsCount
; i
++) {
225 UpdateValidity(true);
230 // We need to update the validity of the fieldset.
231 nsCOMPtr
<nsIConstraintValidation
> cvElmt
= do_QueryObject(aElement
);
232 if (cvElmt
&& cvElmt
->IsCandidateForConstraintValidation() &&
233 !cvElmt
->IsValid()) {
234 UpdateValidity(true);
238 int32_t debugInvalidElementsCount
= 0;
239 for (uint32_t i
= 0; i
< mDependentElements
.Length(); i
++) {
240 HTMLFieldSetElement
* fieldSet
= FromNode(mDependentElements
[i
]);
242 debugInvalidElementsCount
+= fieldSet
->mInvalidElementsCount
;
245 nsCOMPtr
<nsIConstraintValidation
> cvElmt
=
246 do_QueryObject(mDependentElements
[i
]);
247 if (cvElmt
&& cvElmt
->IsCandidateForConstraintValidation() &&
248 !(cvElmt
->IsValid())) {
249 debugInvalidElementsCount
+= 1;
252 MOZ_ASSERT(debugInvalidElementsCount
== mInvalidElementsCount
);
256 void HTMLFieldSetElement::UpdateDisabledState(bool aNotify
) {
257 nsGenericHTMLFormControlElement::UpdateDisabledState(aNotify
);
259 for (nsGenericHTMLFormElement
* element
: mDependentElements
) {
260 element
->FieldSetDisabledChanged(aNotify
);
264 void HTMLFieldSetElement::NotifyElementsForFirstLegendChange(bool aNotify
) {
266 * NOTE: this could be optimized if only call when the fieldset is currently
268 * This should also make sure that mElements is set when we happen to be here.
269 * However, this method shouldn't be called very often in normal use cases.
273 new nsContentList(this, MatchListedElements
, nullptr, nullptr, true);
276 uint32_t length
= mElements
->Length(true);
277 for (uint32_t i
= 0; i
< length
; ++i
) {
278 static_cast<nsGenericHTMLFormElement
*>(mElements
->Item(i
))
279 ->FieldSetFirstLegendChanged(aNotify
);
283 void HTMLFieldSetElement::UpdateValidity(bool aElementValidity
) {
284 if (aElementValidity
) {
285 --mInvalidElementsCount
;
287 ++mInvalidElementsCount
;
290 MOZ_ASSERT(mInvalidElementsCount
>= 0);
292 // The fieldset validity has just changed if:
293 // - there are no more invalid elements ;
294 // - or there is one invalid elmement and an element just became invalid.
295 if (!mInvalidElementsCount
||
296 (mInvalidElementsCount
== 1 && !aElementValidity
)) {
297 AutoStateChangeNotifier
notifier(*this, true);
298 RemoveStatesSilently(ElementState::VALID
| ElementState::INVALID
);
299 AddStatesSilently(mInvalidElementsCount
? ElementState::INVALID
300 : ElementState::VALID
);
303 // We should propagate the change to the fieldset parent chain.
305 mFieldSet
->UpdateValidity(aElementValidity
);
309 JSObject
* HTMLFieldSetElement::WrapNode(JSContext
* aCx
,
310 JS::Handle
<JSObject
*> aGivenProto
) {
311 return HTMLFieldSetElement_Binding::Wrap(aCx
, this, aGivenProto
);
314 } // namespace mozilla::dom