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/. */
6 
7 #include "mozilla/BasicEvents.h"
8 #include "mozilla/EventDispatcher.h"
9 #include "mozilla/EventStates.h"
10 #include "mozilla/Maybe.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"
16 
17 NS_IMPL_NS_NEW_HTML_ELEMENT(FieldSet)
18 
19 namespace mozilla::dom {
20 
HTMLFieldSetElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)21 HTMLFieldSetElement::HTMLFieldSetElement(
22     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
23     : nsGenericHTMLFormControlElement(std::move(aNodeInfo),
24                                       FormControlType::Fieldset),
25       mElements(nullptr),
26       mFirstLegend(nullptr),
27       mInvalidElementsCount(0) {
28   // <fieldset> is always barred from constraint validation.
29   SetBarredFromConstraintValidation(true);
30 
31   // We start out enabled and valid.
32   AddStatesSilently(NS_EVENT_STATE_ENABLED | NS_EVENT_STATE_VALID);
33 }
34 
~HTMLFieldSetElement()35 HTMLFieldSetElement::~HTMLFieldSetElement() {
36   uint32_t length = mDependentElements.Length();
37   for (uint32_t i = 0; i < length; ++i) {
38     mDependentElements[i]->ForgetFieldSet(this);
39   }
40 }
41 
NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement,nsGenericHTMLFormControlElement,mValidity,mElements)42 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement,
43                                    nsGenericHTMLFormControlElement, mValidity,
44                                    mElements)
45 
46 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLFieldSetElement,
47                                              nsGenericHTMLFormControlElement,
48                                              nsIConstraintValidation)
49 
50 NS_IMPL_ELEMENT_CLONE(HTMLFieldSetElement)
51 
52 bool HTMLFieldSetElement::IsDisabledForEvents(WidgetEvent* aEvent) {
53   return IsElementDisabledForEvents(aEvent, nullptr);
54 }
55 
56 // nsIContent
GetEventTargetParent(EventChainPreVisitor & aVisitor)57 void HTMLFieldSetElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
58   // Do not process any DOM events if the element is disabled.
59   aVisitor.mCanHandle = false;
60   if (IsDisabledForEvents(aVisitor.mEvent)) {
61     return;
62   }
63 
64   nsGenericHTMLFormControlElement::GetEventTargetParent(aVisitor);
65 }
66 
AfterSetAttr(int32_t aNameSpaceID,nsAtom * aName,const nsAttrValue * aValue,const nsAttrValue * aOldValue,nsIPrincipal * aSubjectPrincipal,bool aNotify)67 nsresult HTMLFieldSetElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
68                                            const nsAttrValue* aValue,
69                                            const nsAttrValue* aOldValue,
70                                            nsIPrincipal* aSubjectPrincipal,
71                                            bool aNotify) {
72   if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::disabled) {
73     // This *has* to be called *before* calling FieldSetDisabledChanged on our
74     // controls, as they may depend on our disabled state.
75     UpdateDisabledState(aNotify);
76 
77     if (nsINode::GetFirstChild()) {
78       if (!mElements) {
79         mElements = new nsContentList(this, MatchListedElements, nullptr,
80                                       nullptr, true);
81       }
82 
83       uint32_t length = mElements->Length(true);
84       for (uint32_t i = 0; i < length; ++i) {
85         static_cast<nsGenericHTMLFormElement*>(mElements->Item(i))
86             ->FieldSetDisabledChanged(aNotify);
87       }
88     }
89   }
90 
91   return nsGenericHTMLFormControlElement::AfterSetAttr(
92       aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
93 }
94 
GetType(nsAString & aType) const95 void HTMLFieldSetElement::GetType(nsAString& aType) const {
96   aType.AssignLiteral("fieldset");
97 }
98 
99 /* static */
MatchListedElements(Element * aElement,int32_t aNamespaceID,nsAtom * aAtom,void * aData)100 bool HTMLFieldSetElement::MatchListedElements(Element* aElement,
101                                               int32_t aNamespaceID,
102                                               nsAtom* aAtom, void* aData) {
103   nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(aElement);
104   return formControl;
105 }
106 
Elements()107 nsIHTMLCollection* HTMLFieldSetElement::Elements() {
108   if (!mElements) {
109     mElements =
110         new nsContentList(this, MatchListedElements, nullptr, nullptr, true);
111   }
112 
113   return mElements;
114 }
115 
116 // nsIFormControl
117 
Reset()118 nsresult HTMLFieldSetElement::Reset() { return NS_OK; }
119 
InsertChildBefore(nsIContent * aChild,nsIContent * aBeforeThis,bool aNotify,ErrorResult & aRv)120 void HTMLFieldSetElement::InsertChildBefore(nsIContent* aChild,
121                                             nsIContent* aBeforeThis,
122                                             bool aNotify, ErrorResult& aRv) {
123   bool firstLegendHasChanged = false;
124 
125   if (aChild->IsHTMLElement(nsGkAtoms::legend)) {
126     if (!mFirstLegend) {
127       mFirstLegend = aChild;
128       // We do not want to notify the first time mFirstElement is set.
129     } else {
130       // If mFirstLegend is before aIndex, we do not change it.
131       // Otherwise, mFirstLegend is now aChild.
132       const Maybe<uint32_t> indexOfRef =
133           aBeforeThis ? ComputeIndexOf(aBeforeThis) : Some(GetChildCount());
134       const Maybe<uint32_t> indexOfFirstLegend = ComputeIndexOf(mFirstLegend);
135       if ((indexOfRef.isSome() && indexOfFirstLegend.isSome() &&
136            *indexOfRef <= *indexOfFirstLegend) ||
137           // XXX Keep the odd traditional behavior for now.
138           indexOfRef.isNothing()) {
139         mFirstLegend = aChild;
140         firstLegendHasChanged = true;
141       }
142     }
143   }
144 
145   nsGenericHTMLFormControlElement::InsertChildBefore(aChild, aBeforeThis,
146                                                      aNotify, aRv);
147   if (aRv.Failed()) {
148     return;
149   }
150 
151   if (firstLegendHasChanged) {
152     NotifyElementsForFirstLegendChange(aNotify);
153   }
154 }
155 
RemoveChildNode(nsIContent * aKid,bool aNotify)156 void HTMLFieldSetElement::RemoveChildNode(nsIContent* aKid, bool aNotify) {
157   bool firstLegendHasChanged = false;
158 
159   if (mFirstLegend && aKid == mFirstLegend) {
160     // If we are removing the first legend we have to found another one.
161     nsIContent* child = mFirstLegend->GetNextSibling();
162     mFirstLegend = nullptr;
163     firstLegendHasChanged = true;
164 
165     for (; child; child = child->GetNextSibling()) {
166       if (child->IsHTMLElement(nsGkAtoms::legend)) {
167         mFirstLegend = child;
168         break;
169       }
170     }
171   }
172 
173   nsGenericHTMLFormControlElement::RemoveChildNode(aKid, aNotify);
174 
175   if (firstLegendHasChanged) {
176     NotifyElementsForFirstLegendChange(aNotify);
177   }
178 }
179 
AddElement(nsGenericHTMLFormElement * aElement)180 void HTMLFieldSetElement::AddElement(nsGenericHTMLFormElement* aElement) {
181   mDependentElements.AppendElement(aElement);
182 
183   // If the element that we are adding aElement is a fieldset, then all the
184   // invalid elements in aElement are also invalid elements of this.
185   HTMLFieldSetElement* fieldSet = FromNode(aElement);
186   if (fieldSet) {
187     for (int32_t i = 0; i < fieldSet->mInvalidElementsCount; i++) {
188       UpdateValidity(false);
189     }
190     return;
191   }
192 
193   // If the element is a form-associated custom element, adding element might be
194   // caused by FACE upgrade which won't trigger mutation observer, so mark
195   // mElements dirty manually here.
196   CustomElementData* data = aElement->GetCustomElementData();
197   if (data && data->IsFormAssociated() && mElements) {
198     mElements->SetDirty();
199   }
200 
201   // We need to update the validity of the fieldset.
202   nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aElement);
203   if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
204       !cvElmt->IsValid()) {
205     UpdateValidity(false);
206   }
207 
208 #if DEBUG
209   int32_t debugInvalidElementsCount = 0;
210   for (uint32_t i = 0; i < mDependentElements.Length(); i++) {
211     HTMLFieldSetElement* fieldSet = FromNode(mDependentElements[i]);
212     if (fieldSet) {
213       debugInvalidElementsCount += fieldSet->mInvalidElementsCount;
214       continue;
215     }
216     nsCOMPtr<nsIConstraintValidation> cvElmt =
217         do_QueryObject(mDependentElements[i]);
218     if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
219         !(cvElmt->IsValid())) {
220       debugInvalidElementsCount += 1;
221     }
222   }
223   MOZ_ASSERT(debugInvalidElementsCount == mInvalidElementsCount);
224 #endif
225 }
226 
RemoveElement(nsGenericHTMLFormElement * aElement)227 void HTMLFieldSetElement::RemoveElement(nsGenericHTMLFormElement* aElement) {
228   mDependentElements.RemoveElement(aElement);
229 
230   // If the element that we are removing aElement is a fieldset, then all the
231   // invalid elements in aElement are also removed from this.
232   HTMLFieldSetElement* fieldSet = FromNode(aElement);
233   if (fieldSet) {
234     for (int32_t i = 0; i < fieldSet->mInvalidElementsCount; i++) {
235       UpdateValidity(true);
236     }
237     return;
238   }
239 
240   // We need to update the validity of the fieldset.
241   nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aElement);
242   if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
243       !cvElmt->IsValid()) {
244     UpdateValidity(true);
245   }
246 
247 #if DEBUG
248   int32_t debugInvalidElementsCount = 0;
249   for (uint32_t i = 0; i < mDependentElements.Length(); i++) {
250     HTMLFieldSetElement* fieldSet = FromNode(mDependentElements[i]);
251     if (fieldSet) {
252       debugInvalidElementsCount += fieldSet->mInvalidElementsCount;
253       continue;
254     }
255     nsCOMPtr<nsIConstraintValidation> cvElmt =
256         do_QueryObject(mDependentElements[i]);
257     if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
258         !(cvElmt->IsValid())) {
259       debugInvalidElementsCount += 1;
260     }
261   }
262   MOZ_ASSERT(debugInvalidElementsCount == mInvalidElementsCount);
263 #endif
264 }
265 
NotifyElementsForFirstLegendChange(bool aNotify)266 void HTMLFieldSetElement::NotifyElementsForFirstLegendChange(bool aNotify) {
267   /**
268    * NOTE: this could be optimized if only call when the fieldset is currently
269    * disabled.
270    * This should also make sure that mElements is set when we happen to be here.
271    * However, this method shouldn't be called very often in normal use cases.
272    */
273   if (!mElements) {
274     mElements =
275         new nsContentList(this, MatchListedElements, nullptr, nullptr, true);
276   }
277 
278   uint32_t length = mElements->Length(true);
279   for (uint32_t i = 0; i < length; ++i) {
280     static_cast<nsGenericHTMLFormElement*>(mElements->Item(i))
281         ->FieldSetFirstLegendChanged(aNotify);
282   }
283 }
284 
UpdateValidity(bool aElementValidity)285 void HTMLFieldSetElement::UpdateValidity(bool aElementValidity) {
286   if (aElementValidity) {
287     --mInvalidElementsCount;
288   } else {
289     ++mInvalidElementsCount;
290   }
291 
292   MOZ_ASSERT(mInvalidElementsCount >= 0);
293 
294   // The fieldset validity has just changed if:
295   // - there are no more invalid elements ;
296   // - or there is one invalid elmement and an element just became invalid.
297   if (!mInvalidElementsCount ||
298       (mInvalidElementsCount == 1 && !aElementValidity)) {
299     UpdateState(true);
300   }
301 
302   // We should propagate the change to the fieldset parent chain.
303   if (mFieldSet) {
304     mFieldSet->UpdateValidity(aElementValidity);
305   }
306 }
307 
IntrinsicState() const308 EventStates HTMLFieldSetElement::IntrinsicState() const {
309   EventStates state = nsGenericHTMLFormControlElement::IntrinsicState();
310 
311   if (mInvalidElementsCount) {
312     state |= NS_EVENT_STATE_INVALID;
313   } else {
314     state |= NS_EVENT_STATE_VALID;
315   }
316 
317   return state;
318 }
319 
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)320 JSObject* HTMLFieldSetElement::WrapNode(JSContext* aCx,
321                                         JS::Handle<JSObject*> aGivenProto) {
322   return HTMLFieldSetElement_Binding::Wrap(aCx, this, aGivenProto);
323 }
324 
325 }  // namespace mozilla::dom
326