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